高手处理缺失数据:多元和迭代插补算法

多元与迭代插补算法详解

原文:towardsdatascience.com/deal-with-missingness-like-a-pro-multivariate-and-iterative-imputation-algorithms-23f7769da02c

实际世界的数据通常很混乱,在使用任何机器学习(ML)模型之前需要仔细预处理。我们几乎总是会在数据集中遇到空值,如果观察到这些空值,它们可能对我们的分析或建模非常有价值。我们将其称为数据中的缺失

缺失数据可能有各种原因,例如设备故障、ERP 系统中的非必填字段或调查中不适用的问卷问题。根据原因,缺失数据的性质也会有所不同。我们如何理解这种性质将在我之前的文章中详细解释。在这篇文章中,重点是主要如何正确处理这种缺失数据,避免通过删除或插补造成偏差或丢失关键见解。

红葡萄酒质量 数据由 UCI 机器学习仓库 提供,本文[1]中使用。这是一个开源数据集,可以在此链接下载。

理解缺失数据的性质(完全随机缺失、随机缺失、非随机缺失)对于决定正确的处理方法至关重要。因此,如果您认为您需要更多关于这方面的信息,我建议您首先阅读我之前的文章


1. 删除方法

有两种删除方法应谨慎使用,以免丢失任何重要信息:

逐行删除法:为了简化,任何缺失数据的行将被完全删除。

成对删除法:对于特定的分析或模型,对缺失情况进行单独评估。使用所有可用数据而不是完全删除行。

当缺失值的数量非常小(以至于删除它们不会影响分析)或缺失值的数量非常高(> 50%)以至于无法根据可用数据准确预测缺失值时,可以应用删除方法。


2. 插补方法

而不是删除或排除缺失数据,我们可以估计缺失点的最可能值并将其插补。我们可以将插补方法分为两类:

单变量插补是通过仅考虑具有缺失数据的相同单个变量的值来替换空值。这些方法可以在缺失性完全随机(MCAR)的情况下使用。记住,对于 MCAR 情况,数据的缺失性没有任何系统性或与其他变量相关。

另一方面,多元插补考虑数据集中的其他变量来预测最可能的插补值。它不仅仅关注缺失数据的变量。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/1aa5d8a4ea49db3eb3346f3db61232a7.png

Randy FathUnsplash 拍摄的照片

单变量插补方法更易于理解,且相对容易实现。已经有很多优秀的文章可供参考,您可能需要查看更多关于这些插补技术的信息。此外,我们预计多元插补方法在特征选择方面比基本技术提供更好的结果 [2]。这就是为什么我们将专注于本文中的多元插补算法。

您可以在下面看到单变量插补技术的简要描述:

均值/中位数插补:用观察到的均值/中位数替换缺失值。当数据分布接近正态分布时,均值更受欢迎,对于偏斜数据,中位数更受欢迎。

模式插补:用最频繁的值替换缺失值。它适用于分类数据。

前向/后向填充:用前一个或下一个观察到的值替换缺失值。它适用于时间序列数据

随机样本插补:用从数据中抽取的随机值替换。

现在,让我们开始讨论多元插补。我们将在接下来的章节中使用 Python 实现 LightGBM、kNN 和自编码器进行多元插补。我们将使用 ScikitLearn 库进行应用。无论我们实现哪种模型,遵循的步骤都将相似。因此,您可以遵循相同的逻辑,并根据提供的代码调整您的预测模型以估计缺失值。

首先,让我们导入本文中将要使用的所有必需的包:

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Dense
from lightgbm import LGBMRegressor
from sklearn.neighbors import KNeighborsRegressor
from sklearn.metrics import mean_squared_error
from tensorflow.keras.optimizers import Adam
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
from sklearn.linear_model import BayesianRidge
  • 提取数据:
# URL to the dataset (replace this with the actual URL)
path= "winequalityred.csv"

# Read the dataset from a URL or local file path
data = pd.read_csv(path, sep=';', header=0)

print(data.head())

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/1c51b205ce9263398c6577c75079e6e5.png

数据集的前几行

此数据集最初没有空值。我将删除随机值以准备一个包含 NaNs 的数据集来工作:

# Set a random seed for reproducibility
np.random.seed(42)

# Function to set NaNs in random cells
def introduce_random_nans(df, fraction=0.2):

    # Determine number of cells to replace with NaN
    total_cells = df.size
    num_nans = int(total_cells * fraction)  # Fraction of cells to nullify

    # Randomly select indices for rows and columns
    row_indices = np.random.randint(0, df.shape[0], num_nans)
    col_indices = np.random.randint(0, df.shape[1], num_nans)

    # Introduce NaNs
    for r, c in zip(row_indices, col_indices):
        df.iat[r, c] = np.nan

    return df

# Introduce missing values in 20% of the data
df = introduce_random_nans(data, fraction=0.2)

# Check the resulting DataFrame
print(df.head())

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/6e5697a1ac080c32fe6c77baadaca994.png

新数据集中缺失数据的前几行

让我们选择列 ‘sulphates’ 来插补缺失值。我们应该执行预处理步骤。我将编写代码,好像数据既有数值列也有分类列,这样你就可以将其用于其他数据集。

2.1. 使用 Light Gradient Boosting Machine (LightGBM)进行多元插补

简而言之,LightGBM 是实施梯度提升以实现高性能的优化方式。它被广泛用于表格数据的建模,以提高速度并减少内存使用。因此,它是插补数值变量的有效工具。

LGBMClassifier 是一个用于预测分类值的分类算法。LGBMRegressor 是用于估计数值值的回归模型。虽然我们称之为回归,但 LGBMRegressor 不应用任何传统的显式回归模型类型,如线性、多项式或逻辑回归。它使用 决策树 并将它们与 梯度提升框架 结合。更多信息,你可以查看文档

让我们看看我们如何在描述了第二部分步骤后,在 Python 中实现它来处理我们的数据集。

  • 构建 ImputerLightGBM 类:
class ImputerLightGBM:
   def __init__(self, path, target_column, null_fraction=0.2, null_threshold=0.5):
     """
     Class initialization for loading, preprocessing, and imputing data.
     """
     self.path = path
     self.target_column = target_column
     self.null_fraction = null_fraction
     self.null_threshold = null_threshold
     self.data = None
     self.nan_ix = None
     self.scaler = StandardScaler()
     self.model = LGBMRegressor()

   def load_data(self):
     """
     Loads the data from the given file path.
     """
     self.data = pd.read_csv(self.path, sep=';', header=0)
     print("Initial data loaded:")
     print(self.data.head())

   def drop_high_null_columns(self):
     """
     Drop columns with too many NaNs, based on the null_threshold.
     """
     cols_to_drop = [col for col in self.data.columns if
     self.data[col].isnull().sum() > self.null_threshold * len(self.data)]
     self.data.drop(columns=cols_to_drop, inplace=True)
     print(f"Dropped columns: {cols_to_drop}")

   def preprocess_column(self):
     """
     Preprocesses the target column by creating an 'is_nan' indicator column for missing values.
     """
     nan_ix = np.where(self.data[self.target_column].isna())[0]
     data = self.data.copy()
     data['is_nan'] = 0
     data.loc[nan_ix, 'is_nan'] = 1

     # Handle categorical columns
     for cat_col in data.select_dtypes(include='object'):
     data[cat_col], _ = pd.factorize(data[cat_col])

     self.data = data
     self.nan_ix = nan_ix
     print(f"Missing index for {self.target_column}: {nan_ix}")

   def split_and_scale(self, val_size=0.2):
     """
     Splits data into train/validation sets and scales it.
     """
     features = self.data.drop([self.target_column, 'is_nan'], axis=1)
     target = self.data[self.target_column]

     # Separate the test rows (nan_ix)
     X_train_and_val = features.drop(index=self.nan_ix)
     X_test = features.loc[self.nan_ix]
     y_train_and_val = target.drop(index=self.nan_ix)

     # Split the train and validation set
     X_train, X_val, y_train, y_val = train_test_split(
     X_train_and_val, y_train_and_val, test_size=val_size, random_state=42
     )

     # Scale features
     X_train_scaled = self.scaler.fit_transform(X_train)
     X_val_scaled = self.scaler.transform(X_val)
     X_test_scaled = self.scaler.transform(X_test)

     print("Data split and scaling complete.")
     return X_train_scaled, y_train, X_val_scaled, y_val, X_test_scaled

   def fit_and_validate(self, X_train_scaled, y_train, X_val_scaled, y_val):
     """
     Train the model and compute validation RMSE.
     """
     self.model.fit(X_train_scaled, y_train)
     y_val_pred = self.model.predict(X_val_scaled)
     rmse = mean_squared_error(y_val, y_val_pred, squared=False)
     print(f"Validation RMSE: {rmse}")
     return rmse

   def impute_missing_values(self, X_train_and_val, y_train_and_val, X_test_scaled):
     """
     Refit model on all training data and predict/impute missing values in X_test_scaled.
     """
     self.model.fit(X_train_and_val, y_train_and_val)
     y_impute = self.model.predict(X_test_scaled)
     self.data.loc[self.nan_ix, self.target_column] = y_impute
     print("Missing values imputed.")
     return self.data

   def run_pipeline(self):
     """
     Runs the entire pipeline: loading data, introducing NaNs, preprocessing,
     splitting, training, validating, and imputing missing values.
     """
     self.load_data()
     self.introduce_random_nans()
     self.drop_high_null_columns()
     self.preprocess_column()

     # Split and scale data
     X_train_scaled, y_train, X_val_scaled, y_val, X_test_scaled = self.split_and_scale()

     # Train and validate
     self.fit_and_validate(X_train_scaled, y_train, X_val_scaled, y_val)

     # Impute missing values
     self.impute_missing_values(X_train_scaled, y_train, X_test_scaled)

     print("nFinal imputed target column values:")
     print(self.data[self.target_column])
dpath = "winequalityred.csv"
target_column = 'sulphates'

imputer = ImputerLightGBM(path, target_column)
imputer.run_pipeline()

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/394f0f25dffa5aa84b909c6adf3355ad.png

LightGBM Model Information

验证 RMSE (~0.14)可能不是理想的,但仍可用于插补。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/b8b2ca607fa339f896ce4ed858a535fe.png

Sulphates column after LightGBM imputation

让我们也尝试 kNN 模型,看看我们是否会得到更好的结果。

2.2. 使用 K-Nearest Neighbors (kNN)进行多元插补

kNN 是一种监督学习算法,它寻找与实例最接近的 k 个数据点,并预测所需输出的值。如果变量是数值的,它可以使用这些 k 个数据点的平均值/中位数;如果变量是分类的,则使用众数。

为了实现 kNN 算法对相同数据集的插补,我们可以基本上遵循与 LGBMRegressor 模型相似的步骤。这次我们应该应用标准化或归一化进行缩放,因为这是 kNN 的重要预处理步骤。

由于 kNN 算法不接受训练集中的空值,我们在 `preprocess_column 函数中添加了一个额外的行:

  • 构建 ImputerKnn 类
class ImputerKnn:
    def __init__(self, path, target_column='sulphates'):
        """
        Initialize the pipeline with dataset path and target column name.
        Args:
            path: Path to dataset.
            target_column: Target column name for modeling/imputation.
        """
        self.path = path
        self.target_column = target_column
        self.data = None
        self.nan_indices = None
        self.scaler = StandardScaler()
        self.model = None

    def load_data(self):
        """Load dataset from file."""
        self.data = pd.read_csv(self.path, sep=';', header=0)
        print("Loaded data:n", self.data.head())

    def drop_high_null_columns(self, threshold=0.5):
        """Drop columns with missing values above a certain threshold."""
        cols_to_drop = [col for col in self.data.columns if self.data[col].isnull().sum() > threshold * len(self.data)]
        self.data.drop(columns=cols_to_drop, inplace=True)
        print(f"Dropped columns: {cols_to_drop}")
        return self

    def preprocess_column(self):
        """Handle NaN preprocessing and add an indicator for missing values."""
        data = self.data.copy()
        # Drop rows with NaN values in the target column
        data = data[data.drop(columns=[self.target_column]).notnull().all(axis=1)]
        nan_ix = data.index[data[self.target_column].isna()].tolist()
        data['is_nan'] = 0
        data.loc[nan_ix, 'is_nan'] = 1

        for cat_col in data.select_dtypes(include='object'):
            data[cat_col], _ = pd.factorize(data[cat_col])

        self.data = data
        self.nan_indices = nan_ix
        print("Preprocessing complete.")
        return self

    def split_and_scale(self, val_size=0.2):
        """Split the data into training, validation, and test sets and scale features."""
        features = self.data.drop([self.target_column, 'is_nan'], axis=1)
        target = self.data[self.target_column]

        # Separate missing rows for testing
        X_train_and_val = features.drop(index=self.nan_indices)
        X_test = features.loc[self.nan_indices]
        y_train_and_val = target.drop(index=self.nan_indices)

        # Split into train/validation sets
        X_train, X_val, y_train, y_val = train_test_split(
            X_train_and_val, y_train_and_val, test_size=val_size, random_state=42
        )

        # Scale features
        X_train_scaled = self.scaler.fit_transform(X_train)
        X_val_scaled = self.scaler.transform(X_val)
        X_test_scaled = self.scaler.transform(X_test)

        print("Split and scaling complete.")
        return X_train_scaled, y_train, X_val_scaled, y_val, X_test_scaled

    def fit_knn(self, X_train_scaled, y_train, X_val_scaled, y_val, n_neighbors=5):
        """Train KNN model and validate performance."""
        self.model = KNeighborsRegressor(n_neighbors=n_neighbors)
        self.model.fit(X_train_scaled, y_train)
        y_val_pred = self.model.predict(X_val_scaled)
        rmse = mean_squared_error(y_val, y_val_pred, squared=False)
        print(f"Validation RMSE: {rmse}")
        return rmse

    def impute_missing(self, X_test_scaled):
        """Impute missing values using the trained KNN model."""
        imputed_values = self.model.predict(X_test_scaled)
        self.data.loc[self.nan_indices, self.target_column] = imputed_values
        print("Missing values imputed in test set.")
        return self.data

    def run_pipeline(self):
        """
        Orchestrates the entire pipeline in sequence.
        """
        self.load_data()
        self.introduce_random_nans()
        self.drop_high_null_columns()
        self.preprocess_column()
        X_train_scaled, y_train, X_val_scaled, y_val, X_test_scaled = self.split_and_scale()
        self.fit_knn(X_train_scaled, y_train, X_val_scaled, y_val)
        final_data = self.impute_missing(X_test_scaled)

        print("Pipeline executed successfully.")
        print("Final imputed data:n", final_data['sulphates'])
        return final_data
  • 运行最终的插补:
# Initialize the pipeline
path_to_data = "winequalityred.csv"
pipeline = ImputerKnn(path=path_to_data)

# Run the entire pipeline in one call
final_results = pipeline.run_pipeline()

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/5cd850dd1a9a8d5742b240c7c9f49d65.png

kNN Model Information

RMSE 值约为~0.13,略好于 LightGBM 回归。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/15a532f8d68aa4af1406d755e8d675c5.png

Sulphates column after kNN imputation

2.3. 使用自动编码器进行多元插补

自动编码器是设计用来学习数据压缩表示(潜在空间)的无监督神经网络。通过从潜在空间重建输入数据,自动编码器可以有效地建模特征之间的复杂依赖关系。利用这些学习到的表示,我们可以通过根据完整数据中观察到的模式重建它们来估计缺失值。

提供的代码概述了使用自动编码器逐步填充缺失值的流程,从数据预处理和模型训练到应用训练好的自动编码器进行填充。我们不需要为此应用预处理步骤。

  • 构建一个 ImputerAutoencoder 类:
class ImputerAutoencoder:
    def __init__(self, data,
                 recurrent_weight =0.5,
                 optimizer = "adam",
                 dropout_probability = 0.5,
                 hidden_activation = "relu",
                 output_activation = "sigmoid",
                 init = "glorot_normal",
                l1_penalty = 0,
                l2_penalty = 0):
        self.data =data.copy()
        self.recurrent_weight = recurrent_weight
        self.optimizer = optimizer
        self.dropout_probability = dropout_probability
        self.hidden_activation = hidden_activation
        self.output_activation = output_activation
        self.init = init
        self.l1_penalty = l1_penalty
        self.l2_penalty = l2_penalty

    def _get_hidden_layer_sizes(self):
        n_dims =self.data.shape[1]
        return [
            min(2000, 8 * n_dims),
            min(500, 2 * n_dims),
            int(np.ceil(0.5 * n_dims)),
        ]

    def _create_model(self):
        hidden_layer_sizes = self._get_hidden_layer_sizes()
        first_layer_size = hidden_layer_sizes[0]
        n_dims = self.data.shape[1]

        model = Sequential()

        model.add(Dense(
            first_layer_size,
            input_dim =2 * n_dims,
            activation = self.hidden_activation,
            kernel_regularizer=l1_l2(self.l1_penalty, self.l2_penalty),
            kernel_initializer=self.init
            ))

        model.add(Dropout(self.dropout_probability))

        for layer_size in hidden_layer_sizes[1:]:
            model.add(Dense(
                n_dims,
                activation = self.output_activation,
                kernel_regularizer = l1_l2(self.l1_penalty, self.l2_penalty),
                kernel_initializer = self.init
            ))

            loss_fuction = make_reconstruction_loss(n_dims)

            model.compile(optimizer=self.optimizer, loss=loss_fuction)
            return model

    def fill(self, missing_mask):
        self.data[missing_mask] = -1

    def _create_missing_mask(self):
        if self.data.dtype != "f" and self.data.dtype != "d":
            self.data =self.data.astype(float)

        return np.isnan(self.data)

    def _train_epoch(self, model, missing_mask, batch_size):
        input_with_mask = np.hstack([self.data, missing_mask])
        n_samples = len(input_with_mask)
        n_batches = int(np.ceil(n_samples / batch_size))
        indices = np.arange(n_samples)
        np.random.shuffle(indices)
        X_shuffled = input_with_mask[indices]

        for batch_idx in range(n_batches):
            batch_start = batch_idx * batch_size
            batch_end = (batch_idx + 1) * batch_size
            batch_data = X_shuffled[batch_start:batch_end, :]
            model.train_on_batch(batch_data, batch_data)
        return model.predict(input_with_mask)

    def train(self, batch_size=256, train_epochs=100):
        missing_mask = self._create_missing_mask()
        self.fill(missing_mask)
        self.model = self._create_model()

        observed_mask = missing_mask

        for epoch in range(train_epochs):
            X_pred = self._train_epoch(self.model, missing_mask, batch_size)
            observed_mae = masked_mae(X_true=self.data,
                                      X_pred=X_pred,
                                      mask=observed_mask)

            if epoch % 10 == 0:
                print("observed mae:", observed_mae)

            old_weight = (1.0 - self.recurrent_weight)
            self.data[missing_mask] *= old_weight
            pred_missing = X_pred[missing_mask]
            self.data[missing_mask] += self.recurrent_weight * pred_missing

        return self.data.copy()
  • 运行它以预测和填充缺失值:
dmissing_encoded = pd.get_dummies(df)

for col in df.columns:
    missing_cols = missing_encoded.columns.str.startswith(str(col) + "_")
    missing_encoded.loc[df[col].isnull(),missing_cols] =np.nan

imputer = ImputerAutoencoder(missing_encoded.values)
complete_encoded =imputer.train(train_epochs=50, batch_size=10)

df['sulphates'] = complete_encoded[:, df.columns.get_loc('sulphates')]
print(df['sulphates'])

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/a63a04da0624933d403f8a876ad3f1c0.png

均方误差值(50 个 epoch 后约为 0.007)优于 LightGBM 和 kNN 模型。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/30af257dfb9bbbe53adffd437b095918.png

填充后 Sulphates 列


3. 多重插补(MICE)

MICE 是一种迭代过程,用于逐个估计每个变量的缺失值。它将此变量视为其他变量的依赖变量,并在每个步骤中将它们用作预测因子。

假设我们有一个这样的数据集,其中 x 表示单元格不为空,但提供了值,而空单元格是缺失值:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/89ab0363d9e632294efb715e7eb69ef7.png

初始数据集

在我们的第一步中,我们使用选定的预测算法(即线性回归、逻辑回归、K 最近邻等)和列 B、C、D、E 中的值作为预测因子来预测 A 列中的缺失值。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/c3350b6a65dae66f46aee78957f1df6e.png

y 代表 A 中缺失值的预测值

我们逐个重复此步骤,对每个变量的缺失值进行预测,并得到我们的第一次估计。我们使用 A、C、D、E 列中的值来预测 B 中的缺失值;等等。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/1d48126a4343d6d5a0f95d095501e2b9.png

第一阶段预测后填充了预测值的数据集

在得到初步预测后,我们开始进行第二次迭代。我们删除了为列 A 所做的预测,并再次进行预测,这次使用我们创建的新数据集,其中其他变量已填充列。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/04d5ec6f4589e662d184b396f0dbde60.png

用于预测列 A 中缺失值的新数据集

我们得到了列 A 中缺失值的第二次预测,在表中用 z 表示:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/6c021a12ee6261dab0d40da3f228af26.png

第二次迭代以预测 A 中的缺失值

我们逐个重复此步骤,对其他变量(B、C、D、E)进行操作,并完成第二次迭代。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/6db1ec23b6c23461c4c639d8a2b3ca7f.png

第二次迭代完成后的数据集

因此,我们可以重复迭代,直到我们想要的程度,但最常用的迭代次数是 10。这样,预测值就会更接近真实值,错误率会降低。我们也可以在运行算法之前确定一个错误率,在达到它之后,我们可能停止迭代。如果我们这样做,过程会继续,直到插补稳定。

您还可以在文献中看到用于 MICE 的其他术语,例如“完全条件指定”或**“顺序回归多重插补”**[3]。

作为最后的注意事项,重要的是要记住,当数据不是 MAR 时应用 MICE 可能会导致估计偏差[3]。因此,它应该在 MAR 假设下应用。

让我们再讨论一下如何使用ScikitLearnIterativeImputer函数实现MICE

**注意:**ScikitLearn 的 IterativeImputer 仍处于实验状态。未来可能会有一些变化。

在使用 ScikitLearn 进行 MICE 时,可以使用四种类型的估计器:

  • 贝叶斯岭回归

  • 随机森林回归器

  • 创建 Pipeline (Nystoem, Ridge)

  • K 近邻回归器 [4]。

您可以查看ScikitLearn 文档以获取更多信息。在以下代码中,您可以看到我们如何使用贝叶斯岭回归应用 IterativeImputer,使用相同的代码结构。

  • 构建 ImputerMICE 类:
class ImputerMICE:

    def __init__(self, path, target_column='sulphates'):

        self.path = path
        self.target_column = target_column
        self.data = None
        self.scaler = StandardScaler()
        self.nan_indices = None

    def load_data(self):
        self.data = pd.read_csv(self.path, sep=';', header=0)
        print("Loaded data:n", self.data.head())
        return self

    def drop_high_null_columns(self, threshold=0.5):
        # Drop columns with more than the threshold proportion of missing values.
        cols_to_drop = [col for col in self.data.columns if self.data[col].isnull().sum() > threshold * len(self.data)]
        self.data.drop(columns=cols_to_drop, inplace=True)
        print(f"Dropped columns: {cols_to_drop}")
        return self

    def preprocess_column(self):
        #Handle NaN preprocessing and prepare data for iterative imputation.
        data = self.data.copy()
        # Drop rows with NaN values in the target column
        data = data[data.drop(columns=[self.target_column]).notnull().all(axis=1)]
        self.nan_indices = data.index[data[self.target_column].isna()].tolist()

        # Add an indicator for rows that originally had NaNs
        data['is_nan'] = 0
        data.loc[self.nan_indices, 'is_nan'] = 1

        for cat_col in data.select_dtypes(include='object'):
            data[cat_col], _ = pd.factorize(data[cat_col])

        self.data = data
        print("Preprocessing complete.")
        return self

    def split_and_scale(self, val_size=0.2):
        # Split dataset into training/validation/test splits and scale features.
        features = self.data.drop([self.target_column, 'is_nan'], axis=1)
        target = self.data[self.target_column]

        # Separate rows with missing data for testing purposes
        X_train_and_val = features.drop(index=self.nan_indices)
        X_test = features.loc[self.nan_indices]
        y_train_and_val = target.drop(index=self.nan_indices)

        # Split the data into training and validation sets
        X_train, X_val, y_train, y_val = train_test_split(
            X_train_and_val, y_train_and_val, test_size=val_size, random_state=42
        )

        # Scale the features
        X_train_scaled = self.scaler.fit_transform(X_train)
        X_val_scaled = self.scaler.transform(X_val)
        X_test_scaled = self.scaler.transform(X_test)

        print("Split and scaling complete.")
        return X_train_scaled, y_train, X_val_scaled, y_val, X_test_scaled

    def apply_iterative_imputation(self, X_train_scaled, X_val_scaled, X_test_scaled, col_index, estimator=None,
                                   max_iter=10, random_state=42):

        # Applies Iterative Imputation using the specified estimator and updates missing values for a given column.

        if estimator is None:
            estimator = BayesianRidge()

        imputer = IterativeImputer(estimator=estimator, max_iter=max_iter, random_state=random_state)

        # Fit and transform training data and then transform validation/test data
        imputer.fit(X_train_scaled)
        X_train_imputed = imputer.transform(X_train_scaled)
        X_val_imputed = imputer.transform(X_val_scaled)
        X_test_imputed = imputer.transform(X_test_scaled)

        return X_test_imputed[:, col_index]

    def run_iterative_imputation_workflow(self, col_index, estimator=None, max_iter=10, random_state=42):
        #Executes the iterative imputation workflow for a specific column.
        # Preprocess and scale splits
        X_train_scaled, y_train, X_val_scaled, y_val, X_test_scaled = self.split_and_scale()

        # Apply iterative imputation
        imputed_values = self.apply_iterative_imputation(
            X_train_scaled, X_val_scaled, X_test_scaled, col_index,
            estimator=estimator, max_iter=max_iter, random_state=random_state
        )

        # Update missing data in the original DataFrame
        self.data.loc[self.nan_indices, self.target_column] = imputed_values
        print("Iterative imputation workflow complete.")
        return self.data
  • 运行它以预测和插补缺失值:
mice_instance = ImputerMICE(path='winequalityred.csv', target_column='sulphates')
mice_instance.load_data().introduce_random_nans().drop_high_null_columns().preprocess_column()
updated_data = mice_instance.run_iterative_imputation_workflow(col_index=3)

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/70e4e5b30a001c3ad195ccc01634e66b.png

迭代插补后的数据集

结论

重要的是要记住,无论使用哪种插补技术,原始数据都无法恢复。然而,可以使用技术生成尽可能接近现实的数据集[5]。

在本文中,我们探讨了各种多元插补方法以及如何实现迭代插补。通过调整这些技术,我们可以最小化偏差并保持数据完整性,以提高我们最终模型的性能或进行准确的分析。

在下一篇文章中,我们将讨论如何通过比较不同插补技术的结果来确定最适合我们数据集的方法。敬请期待!

所有图像,除非另有说明,均为作者所有。

参考文献

[1] 红酒质量数据,Wine Quality – UCI 机器学习库,2009 年 6 月 10 日(CC BY 4.0)。

[2] Mera-Gaona 等人,评估 MICE 在特征选择中多重插补的影响,doi.org/10.1371/journal.pone.0254720,2021.

[3] Azur, Melissa J 等人. “通过链式方程进行多重插补:它是什么以及它是如何工作的?.” 国际精神病学方法研究杂志 第 20 卷第 1 期 (2011): 40–9. doi:10.1002/mpr.329

[4] 使用 IterativeImputer 的变体进行缺失值插补。www.scikit-learn.org

[5] Kelta, Zoumana. 每个数据科学家都应该知道的处理缺失值的高级技巧。www.datacamp.com, 2023.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值