• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

离散变量编码

互联网 diligentman 1周前 (11-20) 11次浏览

文章目录

  • 无监督编码
    • One-hot编码
      • 独热编码优缺点
      • 调库实现
    • Dummy variable 编码(哑变量)
    • 离散变量 One-hot 编码或哑变量编码的优点
    • Label 编码
  • 有监督编码
    • WOE编码
      • WOE 编码的好处
      • 为什么不直接用WOE做特征选择 而用IV
      • IV
  • 代码实现
    • 数据读取,分割数据集
    • one-hot编码
    • 哑变量编码
    • label 标签编码
    • 自定义标签映射
    • WOE编码
  • 上述源码


离散变量进行编码转换,以进行数值化,其原则是保证编码后变量的
距离可计算且
符合原始变量之间的距离度量.

常用距离公式介绍

离散变量编码

无监督编码

编码的时候和标签无关 — 无监督编码

无监督编码即不需要标签信息,直接对原始离散变量进行变量编码

此处介绍无监督编码常用的 3 种方式:

  • One-hot(独热)编码
  • Dummy variable(哑变量)编码
  • Label(标签)编码

离散变量分为可排序变量和不可排序变量。

  • 排序是指变量间存在等级差异(特征间距离不相等)
比如岗位等级分为普通级、专员级、经理级、
总监级、首席级等,有明显的等级顺序,即其距离是不相等的
  • 不可排序变量是不存在等级差异的(特征间距离相等)
比如性别分为男、女,本质上男与女是没有差异的,
即变量之间的距离是相等的。

One-hot编码

不可排序的离散变量编码 变量与变量之间没有差异

离散变量数值化,也是归一化,数值范围在[0, 1]之内

直接上例子:

以离散变量性别为例,假设它有三种可能的取值{男、女、未知}(很明显这是不可排序变量),变量种类 M=3,采用 3 位二进制向量用 0 或 1 的形式来表示
变量取值,则 One-hot 编码后的结果如下图所示。

离散变量编码

离散变量编码

归一化:消除量纲的影响,加速模型收敛速度

独热编码优缺点

优点

解决了分类器不能处理属性数据也就是字符串数据的问题,在一定程度上也起了特征扩充的作用,它的值只有0和1,不同的类型存储在垂直的空间

缺点

当类别数量很多时,特征空间会非常大,这种情况可以用PCA降维

onehot结果是一个稀疏矩阵,如果类别过多,编码后的矩阵非常稀疏,维度特别高

独热编码用来解决类别性数据的离散问题

树模型不需要one-hot,对于决策树来说,one-hot就是增加树的深度

调库实现

离散变量编码

df1 = pd.read_excel(loc_ftx)  # 读入xls文件
print(df1)

# 特征编码之LabelEncoder
from sklearn.preprocessing import LabelEncoder

le = LabelEncoder()
le.fit(df1['销售区域'])
print(le.classes_)  # 原始标签
print(le.transform(['北京', '广州']))   # 转型后标签
print(le.inverse_transform([0, 2, 3]))  # 将012标签转回来

df1['sale_code'] = le.transform(df1['销售区域'])   # 增加一列,为销售区域的标签编码
print(df1)

离散变量编码


from sklearn.preprocessing import OneHotEncoder
le = OneHotEncoder(sparse=False, categories='auto')    # sparse=False 不用稀疏矩阵
sale_code = le.fit_transform(df1[['sale_code']])  # OHE必须接收二维数据
# pd.get_dummies可以直接把字符串转成数字,而onehotencoder不行
df11 = pd.get_dummies(df1['月'])
# 合并两个dataFrame
print(pd.concat([df1, df11], axis=1))

离散变量编码

Dummy variable 编码(哑变量)

与 One-hot 编码类似,不同的是,哑变量编码用较小的维度来表示变量的取值。如果离散变量的种类有 M 个,哑变量编码只用 M-1 维就可以表示 M 种可能出现的取值

其思想就是一旦训练集确定,离散变量的状态空间就已经确定

比如性别变量只有{男、女、未知}3 种情况,只需要用 2 位二进制表示是男或是女的情况,第 3 种情况一定就是未知,编码后的结果如图所示

离散变量编码

哑变量 没有等距性

离散变量编码

离散变量 One-hot 编码或哑变量编码的优点

  • 使离散变量间的距离可计算,具有离散变量数值化的效果
  • 增加了模型的非线性

Label 编码

在离散变量中,可排序的变量的数值化转换时,如果希望保留等级大小关系,则需要用标签编码(Label 编码)来完成

label编码:可排序的离散变量数值化

例如离散变量学历,其取值为{高中、本科、硕士、博士},下图所示

离散变量编码


有监督编码

如果考虑目标变量,则变量编码的过程可能会使离散变量的数值化过程更具有方向性,这就是有监督编码

WOE编码

WOE(Weight of Evidence)编码就是评分卡中最常用的有监督编码方法。

WOE 编码既可以对离散变量编码,也可以对分箱后的连续变量编码。

数值类型 离散化 –WOE

在评分卡里提高了可解释性

能反映特征的重要程度

WOE:坏样本的分布 / 好样本的分布 取对数

woe值越大 对标签的贡献越大,概率值越大

WOE类似于我们有的时候做独热编码等,相当于用WOE编码值代替原来的取值(原来特征是那样的,现在特征是WOE的特征),用WOE后的新的特征入模,原来特征可以删掉了,提高了数据的一个信息量,提高了模型的一个预测能力

评分卡模型一般要求模型有很好的解释性,因此,Logistic回归模型就是非常好的选择

离散变量编码

求和的作用:这个特征对模型的重要程度,一般用IV

WOE 编码的好处

  • 对波动不敏感
    • 考虑的是一个分布的影响,这一个组对模型的影响,即整体的影响
    • 异常值还可以另外拎出来分一箱
  • 增加变量的可解释性,并且可解释的粒度细化到变量的每个可能取值
  • 可以处理缺失值,将缺失值作为一个特征进行 WOE 编码,免除缺失值插补带来的不确定性

离散变量编码

类别特征数值化
WOE值和目标值正相关,很好的反映了特征对目标的影响

为什么不直接用WOE做特征选择 而用IV

  • 1.WOE值可正可负正负一加可能抵消,正的波动也是波动负的波动也是波动 — 取绝对值
  • 2.样本分布不平衡
    • 如某一组样本很少,WOE值偏大,对结果会有影响

IV

离散变量编码

IV是用来做特征选择的

在WOE前乘一个数(坏样本分布 – 好样本分布)

计算WOE、IV的变量,必须是分箱后的变量,或者不需要分箱的类别型变量,也就是离散型

一般来说某个特征的IV值大于0.02,越大越好,越大说明特征越重要

代码实现

import os
import pandas as pd
import numpy as np
import pickle
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
import warnings

warnings.filterwarnings("ignore")  ##忽略警告

数据读取,分割数据集

# 注意sklearn版本要在v.20.0以上,不同版本函数的位置会不同。
def data_read(data_path, file_name):
    df = pd.read_csv(os.path.join(data_path, file_name), delim_whitespace=True, header=None, engine='python')
    # 变量重命名
    columns = ['status_account', 'duration', 'credit_history', 'purpose', 'amount',
               'svaing_account', 'present_emp', 'income_rate', 'personal_status',
               'other_debtors', 'residence_info', 'property', 'age',
               'inst_plans', 'housing', 'num_credits',
               'job', 'dependents', 'telephone', 'foreign_worker', 'target']
    df.columns = columns

    # 将标签变量由状态1,2转为0,1;0表示好用户,1表示坏用户
    df.target = df.target - 1

    # 数据分为data_train和 data_test两部分,训练集用于得到编码函数,验证集用已知的编码规则对验证集编码
    # x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)
    # stratify: 依据标签y,按原数据y中各类比例,分配给train和test,使得train和test中各类数据的比例与原数据集一样
    data_train, data_test = train_test_split(df, test_size=0.2, random_state=0, stratify=df.target)
    return data_train, data_test
path = r'G:A_实训前置1python_workspacefinance_codechapter5\'
data_path = os.path.join(path, 'data')
file_name = 'german.csv'
# 读取数据
data_train, data_test = data_read(data_path, file_name)
# 不可排序变量
var_no_order = ['credit_history', 'purpose', 'personal_status', 'other_debtors',
                'inst_plans', 'housing', 'job', 'telephone', 'foreign_worker']

one-hot编码

# one—hot编码
# df: 数据框  data_path_1:编码模型保存的位置  flag:数据集
def onehot_encode(df, data_path_1, flag='train'):
    # reset_index:重置索引, drop=True:不想保留原来的index
    df = df.reset_index(drop=True)

    # 判断数据集是否存在缺失值  如果是进行缺失值填补
    # df.isnull().any() 判断哪些列存在缺失值
    if sum(df.isnull().any()) > 0:
        # 数值型和字符串型特征拿出来
        numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']
        var_numerics = df.select_dtypes(include=numerics).columns
        var_str = [i for i in df.columns if i not in var_numerics]

        # 数据类型的缺失值用-77777填补
        if len(var_numerics) > 0:
            df.loc[:, var_numerics] = df[var_numerics].fillna(-7777)

        # 字符串类型的缺失值用NA填补
        if len(var_str) > 0:
            df.loc[:, var_str] = df[var_str].fillna('NA')

    # pickle.dump(obj, file, [,protocol])  序列化对象,将对象obj保存到文件file中去
    # 参数protocol是序列化模式,默认是0(ASCII协议,表示以文本的形式进行序列化
    if flag == 'train':
        enc = OneHotEncoder(dtype='int').fit(df)
        # 保存编码模型
        with open(os.path.join(data_path_1, 'onehot.pkl'), 'wb') as save_model:
            pickle.dump(enc, save_model, 0)

        df_return = pd.DataFrame(enc.transform(df).toarray())
        df_return.columns = enc.get_feature_names(df.columns)

    elif flag == 'test':
        # 测试数据编码
        with open(os.path.join(data_path_1, 'onehot.pkl'), 'rb') as read_model:
            onehot_model = pickle.load(read_model)

        # 如果训练集无缺失值,测试集有缺失值则将该样本删除
        var_range = onehot_model.categories_    # 训练集one-hot编码后的类别种类
        var_name = df.columns
        del_index = []
        for i in range(len(var_range)):
            if 'NA' not in var_range[i] and 'NA' in df[var_name[i]].unique():
                index = np.where(df[var_name[i]] == 'NA')
                del_index.append(index)
            elif -7777 not in var_range[i] and -7777 in df[var_name[i]].unique():
                index = np.where(df[var_name[i]] == -7777)
                del_index.append(index)

        # 删除样本
        if len(del_index) > 0:
            del_index = np.unique(del_index)
            df = df.drop(del_index)
            print('训练集无缺失值,但测试集有缺失值,第{0}条样本被删除'.format(del_index))
        df_return = pd.DataFrame(onehot_model.transform(df).toarray())
        df_return.columns = onehot_model.get_feature_names(df.columns)

    elif flag == 'transform':
        # 编码数据值转化为原始变量
        with open(os.path.join(data_path_1, 'onehot.pkl'), 'rb') as read_model:
            onehot_model = pickle.load(read_model)

        # 逆变换
        df_return = pd.DataFrame(onehot_model.inverse_transform(df))
        df_return.columns = np.unique(['_'.join(i.rsplit('_')[:-1]) for i in df.columns])

    return df_return
# one-hot编码
# 训练数据编码
data_train.credit_history[882] = np.nan
data_train_encode = onehot_encode(data_train[var_no_order], data_path, flag='train')

# 测试集数据编码
data_test.credit_history[529] = np.nan
data_test.purpose[355] = np.nan
data_test_encode = onehot_encode(data_test[var_no_order], data_path, flag='test')

# 查看编码逆变化后的原始变量名
df_encoded = data_test_encode.loc[0:4]
data_inverse = onehot_encode(df_encoded, data_path, flag='transform')

哑变量编码

data_train_dummies = pd.get_dummies(data_train[var_no_order])
data_test_dummies = pd.get_dummies(data_test[var_no_order])
print(data_train_dummies.columns)

# 可排序变量
# 注意,如果分类变量的标签为字符串,这是需要将字符串数值化才可以进行模型训练,标签编码其本质是为
# 标签变量数值化而提出的方法,因此,其值支持单列数据的转化操作,并且转化后的结果是无序的。
# 因此有序变量统一用字典映射的方式完成。
var_order = ['status_account', 'svaing_account', 'present_emp', 'property']

label 标签编码

# 标签编码
def label_encode(df, data_path_1, flag='train'):
    if flag == 'train':
        enc = LabelEncoder().fit(df)
        # 保存编码模型
        with open(os.path.join(data_path_1, 'labelcode.pkl'), 'wb') as save_model:
            pickle.dump(enc, save_model, 0)

        df_return = pd.DataFrame(enc.transform(df))
        df_return.name = df.name

    elif flag == 'test':
        # 测试数据编码
        with open(os.path.join(data_path_1, 'labelcode.pkl'), 'rb') as read_model:
            label_model = pickle.load(read_model)

        df_return = pd.DataFrame(label_model.transform(df))
        df_return.name = df.name

    elif flag == 'transform':
        # 编码数据值转化为原始变量
        with open(os.path.join(data_path_1, 'labelcode.pkl'), 'rb') as read_model:
            label_model = pickle.load(read_model)

        # 逆变换
        df_return = pd.DataFrame(label_model.inverse_transform(df))
    return df_return
# 标签编码
# 训练数据编码
data_train_encode = label_encode(data_train[var_order[1]], data_path, flag='train')

# 验证集数据编码
data_test_encode = label_encode(data_test[var_order[1]], data_path, flag='test')

# 查看编码你变化后的原始变量名
# 后面再改一下
df_encoded = data_test_encode
data_inverse = label_encode(df_encoded, data_path, flag='transform')

自定义标签映射

# 自定义映射
def dict_encode(df, data_path_1):
    # 自定义映射
    embarked_mapping = {}
    embarked_mapping['status_account'] = {'NA': 1, 'A14': 2, 'A11': 3, 'A12': 4, 'A13': 5}
    embarked_mapping['svaing_account'] = {'NA': 1, 'A65': 1, 'A61': 3, 'A62': 5, 'A63': 6, 'A64': 8}
    embarked_mapping['present_emp'] = {'NA': 1, 'A71': 2, 'A72': 5, 'A73': 6, 'A74': 8, 'A75': 10}
    embarked_mapping['property'] = {'NA': 1, 'A124': 1, 'A123': 4, 'A122': 6, 'A121': 9}

    df = df.reset_index(drop=True)

    # 判断数据集是否存在缺失值
    if sum(df.isnull().any()) > 0:
        df = df.fillna('NA')

    # 字典映射
    var_dictEncode = []
    for i in df.columns:
        col = i + '_dictEncode'
        df[col] = df[i].map(embarked_mapping[i])
        var_dictEncode.append(col)
    return df[var_dictEncode]
# 自定义映射
# 训练数据编码
data_train.credit_history[882] = np.nan
data_train_encode = dict_encode(data_train[var_order], data_path)

# 测试集数据编码
data_test.status_account[529] = np.nan
data_test_encode = dict_encode(data_test[var_order], data_path)
print(data_test_encode)

WOE编码

# WOE编码
# 返回某个特征的woe映射后的df、woe字典、iv值
# x:特征   y:类别   target:正样本为1
def woe_cal_trans(x, y, target=1):
    # 计算总体的正负样本数
    p_total = sum(y == target)  # 正样本数
    n_total = len(x) - p_total  # 负样本数
    value_num = list(x.unique())  # 去重后的总数
    woe_map = {}
    iv_value = 0
    for i in value_num:  # 这个特征每种取值的woe值
        # 计算该变量取值箱内的正负样本总数
        y1 = y[np.where(x == i)[0]]
        p_num_1 = sum(y1 == target)
        n_num_1 = len(y1) - p_num_1
        # 计算占比
        good_1 = p_num_1 / p_total  # 好样本分布率
        bad_1 = n_num_1 / n_total  # 坏样本分布率
        if bad_1 == 0:
            bad_1 = 1e-5
        elif good_1 == 0:
            good_1 = 1e-5
        woe_map[i] = np.log(bad_1 / good_1)  # woe值
        iv_value += (bad_1 - good_1) * woe_map[i]  # iv值
    x_woe_trans = x.map(woe_map)
    x_woe_trans.name = x.name + "_woe"

    return x_woe_trans, woe_map, iv_value

# WOE编码映射
def woe_encode(df, data_path_1, varnames, y, filename, flag='train'):
    """
    Param:
    df: 待编码数据
    data_path_1 :存取文件路径
    varnames: 变量列表
    y:  目标变量
    filename:编码存取的文件名
    flag: 选择训练还是测试
    ---------------------------------------
    Return:
    df: 编码后的数据,包含了原始数据
    woe_maps: 编码字典
    iv_values: 每个变量的IV值
    var_woe_name: 每个特征拼接woe的列名
    """
    df = df.reset_index(drop=True)  # 重置索引,不保留原来的索引

    # 判断数据集是否存在缺失值
    if sum(df.isnull().any()) > 0:
        numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']
        var_numerics = df.select_dtypes(include=numerics).columns       # 数值型特征
        var_str = [i for i in df.columns if i not in var_numerics]      # 字符串型特征
        # 数据类型的缺失值用-77777填补
        if len(var_numerics) > 0:
            df.loc[:, var_numerics] = df[var_numerics].fillna(-7777)
        # 字符串类型的缺失值用NA填补
        if len(var_str) > 0:
            df.loc[:, var_str] = df[var_str].fillna('NA')

    if flag == 'train':
        iv_values = {}  # 保存每个特征的iv值
        woe_maps = {}   # 保存每个特征的woe值
        var_woe_name = []
        for var in varnames:    # 遍历每一个特征
            x = df[var]
            # 变量映射
            x_woe_trans, woe_map, info_value = woe_cal_trans(x, y)
            var_woe_name.append(x_woe_trans.name)
            df = pd.concat([df, x_woe_trans], axis=1)   # 按行拼接woe值
            woe_maps[var] = woe_map
            iv_values[var] = info_value

        # 保存woe映射字典
        with open(os.path.join(data_path_1, filename + '.pkl'), 'wb') as save_woe_dict:
            pickle.dump(woe_maps, save_woe_dict, 0)

        return df, woe_maps, iv_values, var_woe_name

    elif flag == 'test':
        # 测试数据编码
        with open(os.path.join(data_path_1, filename + '.pkl'), 'rb') as read_woe_dict:
            woe_dict = pickle.load(read_woe_dict)

        # 如果训练集无缺失值,测试集有缺失值则将该样本删除
        woe_dict.keys()
        del_index = []
        for key, value in woe_dict.items():
            if 'NA' not in value.keys() and 'NA' in df[key].unique():
                index = np.where(df[key] == 'NA')
                del_index.append(index)
            elif -7777 not in value.keys() and -7777 in df[key].unique():
                index = np.where(df[key] == -7777)
                del_index.append(index)
        # 删除样本
        if len(del_index) > 0:
            del_index = np.unique(del_index)
            df = df.drop(del_index)
            print('训练集无缺失值,但测试集有缺失值,该样本{0}删除'.format(del_index))

        # WOE编码映射
        var_woe_name = []
        for key, value in woe_dict.items():
            val_name = key + "_woe"
            df[val_name] = df[key].map(value)
            var_woe_name.append(val_name)

        return df, var_woe_name
# WOE编码
# 训练集WOE编码
df_train_woe, dict_woe_map, dict_iv_values, var_woe_name = woe_encode(data_train, data_path, var_no_order,
                                                                      data_train.target, 'dict_woe_map',
                                                                      flag='train')

# 测试集WOE编码
df_test_woe, var_woe_name = woe_encode(data_test, data_path, var_no_order, data_train.target, 'dict_woe_map',
                                       flag='test')

上述源码

# -*- coding: utf-8 -*-
"""
变量编码:one-hot编码、标签编码、自定义字典映射、woe编码
"""
import os
import pandas as pd
import numpy as np
import pickle
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
import warnings

warnings.filterwarnings("ignore")  ##忽略警告


# 注意sklearn版本要在v.20.0以上,不同版本函数的位置会不同。
def data_read(data_path, file_name):
    df = pd.read_csv(os.path.join(data_path, file_name), delim_whitespace=True, header=None, engine='python')
    # 变量重命名
    columns = ['status_account', 'duration', 'credit_history', 'purpose', 'amount',
               'svaing_account', 'present_emp', 'income_rate', 'personal_status',
               'other_debtors', 'residence_info', 'property', 'age',
               'inst_plans', 'housing', 'num_credits',
               'job', 'dependents', 'telephone', 'foreign_worker', 'target']
    df.columns = columns

    # 将标签变量由状态1,2转为0,1;0表示好用户,1表示坏用户
    df.target = df.target - 1

    # 数据分为data_train和 data_test两部分,训练集用于得到编码函数,验证集用已知的编码规则对验证集编码
    # x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)
    # stratify: 依据标签y,按原数据y中各类比例,分配给train和test,使得train和test中各类数据的比例与原数据集一样
    data_train, data_test = train_test_split(df, test_size=0.2, random_state=0, stratify=df.target)
    return data_train, data_test


# one—hot编码
# df: 数据框  data_path_1:编码模型保存的位置  flag:数据集
def onehot_encode(df, data_path_1, flag='train'):
    # reset_index:重置索引, drop=True:不想保留原来的index
    df = df.reset_index(drop=True)

    # 判断数据集是否存在缺失值  如果是进行缺失值填补
    # df.isnull().any() 判断哪些列存在缺失值
    if sum(df.isnull().any()) > 0:
        # 数值型和字符串型特征拿出来
        numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']
        var_numerics = df.select_dtypes(include=numerics).columns
        var_str = [i for i in df.columns if i not in var_numerics]

        # 数据类型的缺失值用-77777填补
        if len(var_numerics) > 0:
            df.loc[:, var_numerics] = df[var_numerics].fillna(-7777)

        # 字符串类型的缺失值用NA填补
        if len(var_str) > 0:
            df.loc[:, var_str] = df[var_str].fillna('NA')

    # pickle.dump(obj, file, [,protocol])  序列化对象,将对象obj保存到文件file中去
    # 参数protocol是序列化模式,默认是0(ASCII协议,表示以文本的形式进行序列化
    if flag == 'train':
        enc = OneHotEncoder(dtype='int').fit(df)
        # 保存编码模型
        with open(os.path.join(data_path_1, 'onehot.pkl'), 'wb') as save_model:
            pickle.dump(enc, save_model, 0)

        df_return = pd.DataFrame(enc.transform(df).toarray())
        df_return.columns = enc.get_feature_names(df.columns)

    elif flag == 'test':
        # 测试数据编码
        with open(os.path.join(data_path_1, 'onehot.pkl'), 'rb') as read_model:
            onehot_model = pickle.load(read_model)

        # 如果训练集无缺失值,测试集有缺失值则将该样本删除
        var_range = onehot_model.categories_  # 训练集one-hot编码后的类别种类
        var_name = df.columns
        del_index = []
        for i in range(len(var_range)):
            if 'NA' not in var_range[i] and 'NA' in df[var_name[i]].unique():
                index = np.where(df[var_name[i]] == 'NA')
                del_index.append(index)
            elif -7777 not in var_range[i] and -7777 in df[var_name[i]].unique():
                index = np.where(df[var_name[i]] == -7777)
                del_index.append(index)

        # 删除样本
        if len(del_index) > 0:
            del_index = np.unique(del_index)
            df = df.drop(del_index)
            print('训练集无缺失值,但测试集有缺失值,第{0}条样本被删除'.format(del_index))
        df_return = pd.DataFrame(onehot_model.transform(df).toarray())
        df_return.columns = onehot_model.get_feature_names(df.columns)

    elif flag == 'transform':
        # 编码数据值转化为原始变量
        with open(os.path.join(data_path_1, 'onehot.pkl'), 'rb') as read_model:
            onehot_model = pickle.load(read_model)

        # 逆变换
        df_return = pd.DataFrame(onehot_model.inverse_transform(df))
        df_return.columns = np.unique(['_'.join(i.rsplit('_')[:-1]) for i in df.columns])

    return df_return


# 标签编码
def label_encode(df, data_path_1, flag='train'):
    if flag == 'train':
        enc = LabelEncoder().fit(df)
        # 保存编码模型
        with open(os.path.join(data_path_1, 'labelcode.pkl'), 'wb') as save_model:
            pickle.dump(enc, save_model, 0)

        df_return = pd.DataFrame(enc.transform(df))
        df_return.name = df.name

    elif flag == 'test':
        # 测试数据编码
        with open(os.path.join(data_path_1, 'labelcode.pkl'), 'rb') as read_model:
            label_model = pickle.load(read_model)

        df_return = pd.DataFrame(label_model.transform(df))
        df_return.name = df.name

    elif flag == 'transform':
        # 编码数据值转化为原始变量
        with open(os.path.join(data_path_1, 'labelcode.pkl'), 'rb') as read_model:
            label_model = pickle.load(read_model)

        # 逆变换
        df_return = pd.DataFrame(label_model.inverse_transform(df))
    return df_return


# 自定义映射
def dict_encode(df, data_path_1):
    # 自定义映射
    embarked_mapping = {}
    embarked_mapping['status_account'] = {'NA': 1, 'A14': 2, 'A11': 3, 'A12': 4, 'A13': 5}
    embarked_mapping['svaing_account'] = {'NA': 1, 'A65': 1, 'A61': 3, 'A62': 5, 'A63': 6, 'A64': 8}
    embarked_mapping['present_emp'] = {'NA': 1, 'A71': 2, 'A72': 5, 'A73': 6, 'A74': 8, 'A75': 10}
    embarked_mapping['property'] = {'NA': 1, 'A124': 1, 'A123': 4, 'A122': 6, 'A121': 9}

    df = df.reset_index(drop=True)

    # 判断数据集是否存在缺失值
    if sum(df.isnull().any()) > 0:
        df = df.fillna('NA')

    # 字典映射
    var_dictEncode = []
    for i in df.columns:
        col = i + '_dictEncode'
        df[col] = df[i].map(embarked_mapping[i])
        var_dictEncode.append(col)
    return df[var_dictEncode]


# WOE编码
# 返回某个特征的woe映射后的df、woe字典、iv值
def woe_cal_trans(x, y, target=1):
    # 计算总体的正负样本数
    p_total = sum(y == target)  # 负样本数
    n_total = len(x) - p_total  # 正样本数
    value_num = list(x.unique())  # 去重后的总数
    woe_map = {}
    iv_value = 0
    for i in value_num:  # 这个特征每种取值的woe值
        # 计算该变量取值箱内的正负样本总数
        y1 = y[np.where(x == i)[0]]
        p_num_1 = sum(y1 == target)
        n_num_1 = len(y1) - p_num_1
        # 计算占比
        bad_1 = p_num_1 / p_total  # 坏样本分布率
        good_1 = n_num_1 / n_total  # 好样本分布率
        if bad_1 == 0:
            bad_1 = 1e-5
        elif good_1 == 0:
            good_1 = 1e-5
        woe_map[i] = np.log(bad_1 / good_1)  # woe值
        iv_value += (bad_1 - good_1) * woe_map[i]  # iv值
    x_woe_trans = x.map(woe_map)
    x_woe_trans.name = x.name + "_woe"
    print(x_woe_trans)
    return x_woe_trans, woe_map, iv_value


# WOE编码映射
def woe_encode(df, data_path_1, varnames, y, filename, flag='train'):
    """
    Param:
    df: 待编码数据
    data_path_1 :存取文件路径
    varnames: 变量列表
    y:  目标变量
    filename:编码存取的文件名
    flag: 选择训练还是测试
    ---------------------------------------
    Return:
    df: 编码后的数据,包含了原始数据
    woe_maps: 编码字典
    iv_values: 每个变量的IV值
    var_woe_name: 每个特征拼接woe的列名
    """
    df = df.reset_index(drop=True)  # 重置索引,不保留原来的索引

    # 判断数据集是否存在缺失值
    if sum(df.isnull().any()) > 0:
        numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']
        var_numerics = df.select_dtypes(include=numerics).columns  # 数值型特征
        var_str = [i for i in df.columns if i not in var_numerics]  # 字符串型特征
        # 数据类型的缺失值用-77777填补
        if len(var_numerics) > 0:
            df.loc[:, var_numerics] = df[var_numerics].fillna(-7777)
        # 字符串类型的缺失值用NA填补
        if len(var_str) > 0:
            df.loc[:, var_str] = df[var_str].fillna('NA')

    if flag == 'train':
        iv_values = {}  # 保存每个特征的iv值
        woe_maps = {}  # 保存每个特征的woe值
        var_woe_name = []
        for var in varnames:  # 遍历每一个特征
            x = df[var]
            # 变量映射
            x_woe_trans, woe_map, info_value = woe_cal_trans(x, y)
            var_woe_name.append(x_woe_trans.name)
            df = pd.concat([df, x_woe_trans], axis=1)  # 按行拼接woe值
            woe_maps[var] = woe_map
            iv_values[var] = info_value

        # 保存woe映射字典
        with open(os.path.join(data_path_1, filename + '.pkl'), 'wb') as save_woe_dict:
            pickle.dump(woe_maps, save_woe_dict, 0)

        return df, woe_maps, iv_values, var_woe_name

    elif flag == 'test':
        # 测试数据编码
        with open(os.path.join(data_path_1, filename + '.pkl'), 'rb') as read_woe_dict:
            woe_dict = pickle.load(read_woe_dict)

        # 如果训练集无缺失值,测试集有缺失值则将该样本删除
        woe_dict.keys()
        del_index = []
        for key, value in woe_dict.items():
            if 'NA' not in value.keys() and 'NA' in df[key].unique():
                index = np.where(df[key] == 'NA')
                del_index.append(index)
            elif -7777 not in value.keys() and -7777 in df[key].unique():
                index = np.where(df[key] == -7777)
                del_index.append(index)
        # 删除样本
        if len(del_index) > 0:
            del_index = np.unique(del_index)
            df = df.drop(del_index)
            print('训练集无缺失值,但测试集有缺失值,该样本{0}删除'.format(del_index))

        # WOE编码映射
        var_woe_name = []
        for key, value in woe_dict.items():
            val_name = key + "_woe"
            df[val_name] = df[key].map(value)
            var_woe_name.append(val_name)

        return df, var_woe_name


if __name__ == '__main__':
    path = r'G:A_实训前置1python_workspacefinance_codechapter5\'
    data_path = os.path.join(path, 'data')
    file_name = 'german.csv'
    # 读取数据
    data_train, data_test = data_read(data_path, file_name)
    # 不可排序变量
    var_no_order = ['credit_history', 'purpose', 'personal_status', 'other_debtors',
                    'inst_plans', 'housing', 'job', 'telephone', 'foreign_worker']

    # # one-hot编码
    # # 训练数据编码
    # data_train.credit_history[882] = np.nan
    # data_train_encode = onehot_encode(data_train[var_no_order], data_path, flag='train')
    #
    # # 测试集数据编码
    # data_test.credit_history[529] = np.nan
    # data_test.purpose[355] = np.nan
    # data_test_encode = onehot_encode(data_test[var_no_order], data_path, flag='test')
    #
    #
    #
    # # 查看编码逆变化后的原始变量名
    # df_encoded = data_test_encode.loc[0:4]
    # data_inverse = onehot_encode(df_encoded, data_path, flag='transform')
    # print(data_inverse)

    # # 哑变量编码
    # data_train_dummies = pd.get_dummies(data_train[var_no_order])
    # data_test_dummies = pd.get_dummies(data_test[var_no_order])
    # print(data_train_dummies.columns)

    # 可排序变量
    # 注意,如果分类变量的标签为字符串,这是需要将字符串数值化才可以进行模型训练,标签编码其本质是为
    # 标签变量数值化而提出的方法,因此,其值支持单列数据的转化操作,并且转化后的结果是无序的。
    # 因此有序变量统一用字典映射的方式完成。
    var_order = ['status_account', 'svaing_account', 'present_emp', 'property']

    # # 标签编码
    # # 训练数据编码
    # data_train_encode = label_encode(data_train[var_order[1]], data_path, flag='train')
    #
    # # 验证集数据编码
    # data_test_encode = label_encode(data_test[var_order[1]], data_path, flag='test')
    #
    # # 查看编码你变化后的原始变量名
    # # 后面再改一下
    # df_encoded = data_test_encode
    # data_inverse = label_encode(df_encoded, data_path, flag='transform')

    # # 自定义映射
    # # 训练数据编码
    # data_train.credit_history[882] = np.nan
    # data_train_encode = dict_encode(data_train[var_order], data_path)
    #
    # # 测试集数据编码
    # data_test.status_account[529] = np.nan
    # data_test_encode = dict_encode(data_test[var_order], data_path)
    # print(data_test_encode)

    # WOE编码
    # 训练集WOE编码
    df_train_woe, dict_woe_map, dict_iv_values, var_woe_name = woe_encode(data_train, data_path, var_no_order,
                                                                          data_train.target, 'dict_woe_map',
                                                                          flag='train')
    # print(df_train_woe, 'n')
    # print(dict_woe_map, 'n')
    # print(dict_iv_values, 'n')
    # print(var_woe_name, 'n')

    # 测试集WOE编码
    df_test_woe, var_woe_name = woe_encode(data_test, data_path, var_no_order, data_train.target, 'dict_woe_map',
                                           flag='test')


喜欢 (0)