解决机器学习中数据缺失问题的方法

机器算法验证 机器学习 scikit-学习 缺失数据 数据插补
2022-01-24 17:39:10

几乎任何我们想要使用机器学习算法进行预测的数据库都会发现一些特征的缺失值。

有几种方法可以解决这个问题,排除具有缺失值的线,直到它们填充特征的平均值。

我想使用一种更稳健的方法,它基本上会运行回归(或另一种方法),其中因变量(Y)将是具有缺失值的每一列,但仅限于表的行包含所有数据,并使用此方法预测缺失值,逐表完成表格并移动到具有缺失值的下一个“列”并重复该方法,直到填满所有内容。

但这让我有些怀疑。

为什么任何列开始?我相信缺失值最小的那个一直到最多的那个

是否存在不值得尝试完成的缺失值阈值?(例如,如果这个特征只有 10% 的值被填充,排除它会不会更有趣)

传统包或其他方法中是否有任何类型的实现对缺失具有鲁棒性?

3个回答

您描述的技术称为通过顺序回归进行的插补或通过链式方程进行的多重插补。该技术由 Raghunathan (2001) 首创,并在名为mice(van Buuren, 2012) 的运行良好的 R 包中实施。

Schafer 和 Graham (2002) 的一篇论文很好地解释了为什么平均插补和列表删除(你称之为行排除)通常不是上述技术的良好替代品。主要是均值插补不是有条件的,因此可以将插补分布偏向观察到的均值。它还将缩小方差,以及对估算分布的其他不良影响。此外,列表删除确实只有在数据完全随机丢失的情况下才会起作用,比如抛硬币。随着样本量的减少,它也会增加抽样误差。

上面引用的作者通常建议从具有最少缺失值的变量开始。此外,该技术通常以贝叶斯方式应用(即您的建议的扩展)。在插补过程中更频繁地访问变量,而不仅仅是一次。特别是,每个变量都是通过提取其条件后验预测分布来完成的,从具有最小缺失值的变量开始。一旦完成数据集中的所有变量,算法再次从第一个变量开始,然后重新迭代直到收敛。作者已经证明该算法是 Gibbs 算法,因此它通常会收敛到变量的正确多元分布。

通常,因为涉及一些不可检验的假设,特别是随机数据缺失(即是否观察到数据仅取决于观察到的数据,而不取决于未观察到的值)。此外,这些程序可能部分不兼容,这就是为什么它们被称为 PIGS(部分不兼容的 Gibbs 采样器)。

在实践中,贝叶斯多重插补仍然是处理多元非单调缺失数据问题的好方法。此外,预测均值匹配等非参数扩展有助于放宽回归建模假设。


Raghunathan, TE, Lepkowski, J., van Hoewyk, J., & Solenberger, P. (2001)。一种使用一系列回归模型对缺失值进行多重插补的多元技术。调查方法,27(1),85-95。

谢弗,JL 和格雷厄姆,JW(2002 年)。缺失数据:我们对最新技术的看法。心理学方法,7(2),147-177。https://doi.org/10.1037/1082-989X.7.2.147

范布伦,S.(2012 年)。缺失数据的灵活插补。博卡拉顿:CRC 出版社。

我没有找到任何可以解决我的问题的方法,所以我编写了一个函数,它混合了 Pandas 数据框的一些解决方案,其中缺少数值(带有花式输入)和分类(带有随机森林)。

import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier
import fancyimpute as fi

def separe_numeric_categoric(df):
    numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']
    df_n = df.select_dtypes(include=numerics)
    df_c = df.select_dtypes(exclude=numerics)
    print(f'The DF have {len(list(df_n))} numerical features and {len(list(df_c))} categorical fets')
    return df_n, df_c


def find_missing(df):
    total = df.isnull().sum().sort_values(ascending=False)
    percent = (df.isnull().sum()/df.isnull().count()).sort_values(ascending=False)
    filter(lambda x: x>=minimum, percent)
    return percent


def count_missing(df):
    missing = find_missing(df)
    total_columns_with_missing = 0
    for i in (missing):
        if i>0:
            total_columns_with_missing += 1
    return total_columns_with_missing


def remove_missing_data(df,minimum=.1):
    percent = find_missing(df)
    number = len(list(filter(lambda x: x>=(1.0-minimum), percent)))
    names = list(percent.keys()[:number])
    df = df.drop(names, 1, errors='ignore')
    print(f'{number} columns exclude because haven`t minimium data.')
    return df


def one_hot(df, cols):
    for each in cols:
        dummies = pd.get_dummies(df[each], prefix=each, drop_first=False)
        df = pd.concat([df, dummies], axis=1)
    df = df.drop(cols, axis=1)
    return df



def impute_missing_data(df,minimium_data=.1):
    columns_missing = count_missing(df)
    print(f'Total columns with missing values: {count_missing(df)} of a {len(list(df))} columns in df')

    # remove features without minimium size of information
    df = remove_missing_data(df,minimium_data)

    numerical_df, categorical_df = separe_numeric_categoric(df)

    # Autocomplete using MICE for numerical features.
    try:
        df_numerical_complete = fi.MICE(verbose=False).complete(numerical_df.values)
        n_missing = count_missing(df)
        print(f'{columns_missing-n_missing} numerical features imputated')

        # Complete the columns name.
        temp = pd.DataFrame(columns=numerical_df.columns, data=df_numerical_complete)

        # df temp com os dados numericos completados e os categóricos.
        df = pd.concat([temp, categorical_df], axis=1)

    except Exception as e:
        print(e)
        print('Without Missing data in numerical features')

    missing = find_missing(df)
    names = missing.keys()
    n = 0
    for i, c in enumerate(missing):
        if c > 0:
            col = names[i]
            print(f'Start the prediction of {col}')
            clf = RandomForestClassifier()
            le = LabelEncoder()
            ## inverter a ordem da predição das categóricas pode melhorar a precisao.
            categorical_train = list(categorical_df.loc[:,categorical_df.columns != col])

            temp = one_hot(df,categorical_train)
            df1 = temp[temp[col].notnull()]
            df2 = temp[temp[col].isnull()]
            df1_x = df1.loc[:, df1.columns != col]
            df2_x = df2.loc[:, df1.columns != col]

            df1_y = df1[col]
            le.fit(df1_y)
            df1_y = le.transform(df1_y)
            clf.fit(df1_x, df1_y)
            df2_yHat = clf.predict(df2_x)
            df2_yHat = le.inverse_transform(df2_yHat)
            df2_yHat = pd.DataFrame(data=df2_yHat, columns=[col])
            df1_y = le.inverse_transform(df1_y)
            df1_y = pd.DataFrame(data=df1_y,columns=[col])

            df2_x.reset_index(inplace=True)   
            result2 = pd.concat([df2_yHat, df2_x], axis=1)
            try:
                del result2['index']
            except:
                pass

            df1_x.reset_index(inplace=True)
            result1 = pd.concat([df1_y, df1_x], axis=1)
            try:
                del result1['index']
            except:
                pass

            result = pd.concat([result1, result2])
            result = result.set_index(['Id'])
            df.reset_index()            
            try:
                df.set_index(['Id'],inplace=True)
            except:
                pass
            df[col] = result[col]

            n += 1

    print(f'Number of columns categorical with missing data solved: {n}')

    return df


df = impute_missing_data(df)

虽然通常涉及更多,但您可以尝试根据您拥有的数据创建最大熵分布。

http://proceedings.mlr.press/v5/huang09a/huang09a.pdf