使用 KNNImputer 填充 Pandas 中缺失的数据

时间:2021-06-21 18:18:24

标签: python pandas dataframe knn

我有一个包含不同城市属性的数据框。我正在尝试使用 KNNImputer 填充缺失的数据。我在选择 K 值时遇到问题。因此,对于 K 的每个值,我尝试使用 KNNImputer 来拟合和转换原始数据帧。然后我从完整的数据框中删除了随机记录,并使用原始输入器来填充新的缺失值。然后我计算原始填充数据帧和随机填充数据帧之间的差异以找到错误,假设我会选择错误最小的 K 值。

error=[]
for s in strategies:
    imputer =  KNNImputer(n_neighbors=int(s))
    transformed_df = pd.DataFrame(imputer.fit_transform(X))
    dropped_rows, dropped_cols = np.random.choice(ma_water_numeric.shape[0], 10, replace=False), np.random.choice(ma_water_numeric.shape[1], 10, replace=False)
    compare_df = transformed_df.copy()
    for i in np.arange(10):
        compare_df.iat[dropped_rows[i], dropped_cols[i]]=np.nan
    compare_df = imputer.transform(compare_df)
    error.append(sum(abs(compare_df-transformed_df)))
    

我现在对 K 的所有值都遇到相同的错误。 是否有更好的方法来执行此操作或内置方法来检查 K 的适当值? 谢谢

1 个答案:

答案 0 :(得分:0)

为什么在整个迭代过程中出现相同的错误?

每次迭代都出现相同错误的原因是您没有调用正确的 sum 方法。

for n_rows in range(100):
    range_df = pd.DataFrame(np.random.randint(0, 15, (10,n_rows)))
    range_df2 = pd.DataFrame(np.random.randint(0, 15, (10,n_rows))
    assert sum(abs(range_df-range_df2)) == sum(range(n_rows))
# > No AssertionError

您正在做的是对索引求和,在本例中,索引是从 0 到 n_rows 的整数范围。如果所有错误都得到 66,那么您的 DataFrame 显然有 12 行:

sum(range(12))
# > 66

要获得所需的错误,您可以将最后一行替换为:

error.append(abs(compare_df-transformed_df).sum().sum())

除此之外,还有什么问题吗?

1.比较对象

您将两个预测放在一起比较,而不是将预测与真实情况进行比较。

您的输入者越“笨”,做出相同预测的可能性就越大。

假设它总是预测相同的值,这是模型可以得到的最愚蠢的值。它的预测值,不考虑不变值,彼此之间的准确度为 100%。

我们必须将预测与预期值进行比较,才能判断其准确性,而这正是我们经常想要的。

您通常会将训练数据一分为二(不一定具有相同的长度):一分用于拟合,另一分用于验证。

2.随机化在循环内

随机化是……好吧,随机化。如果我们比较它们的数据不断变化,你就不能一致地说一个模型是否比另一个更好。

您应该在训练循环之外进行“nan 值填充”。

3.使用 DataFrames 而不是 numpy 数组

通常,我们使用最方便的形式,然后在需要时对其进行格式化。

事实上,如果您在迭代中使用了 numpy 数组,您就不会得到本答案第一部分中提到的错误。

有什么更好的方法吗?

一些背景

您的意图是所谓的超参数调整。有几种策略可以做到这一点,我们可以从中做出相对于总处理时间的选择。 Here are some of those strategies。如果没有以实现的形式存在,也可以制定自己的策略来满足特定需求,但他们仍然会在 scikit_learn 中找到方便的类来帮助他们。

由于您的数据量不大,我们可以在一定范围内尝试所有组合。

让我们回顾一下

  1. 我们希望有真实数据来比较所有预测。

  2. 我们想要拆分,以便我们可以衡量模型的真实准确度。我将假设您的数据框是整个训练过程的可用数据。

  3. 我们需要准确的方法来衡量准确度并进行比较。

让我们练习

进口

import numpy as np
from sklearn.model_selection import train_test_split, ParameterGrid
from sklearn.impute import KNNImputer

数据准备

我们将使用全能的train_test_split。 我们完整的数据集是 y_true (ground_truth)。填充了 nans 的数据集是我们的 X。我们将两者一分为二:一个用于训练的 X_train 用于训练(y_train 作为真实值),一个用于验证的 X_val 用于验证(y_val 作为真实值)。

# ma_water_numeric is our complete validation dataframe.
# X_copy will be our training data.
# Let's use numpy arrays.
X = ma_water_numeric.values

# Now we fill with nans with your method, but outside the loop.
dropped_rows = np.random.choice(X.shape[0], 10, replace=False)
dropped_cols = np.random.choice(X.shape[1], 10, replace=False)
for r, c in zip(dropped_rows, dropped_cols):
    X[r, c]=np.nan

# We split: 20% spare for the validation, 80% for the learning part,
# with a constant random state to make it reproducible.
X_train, X_val, y_train, y_val = train_test_split(X, ma_water_numeric.values, test_size=0.2, random_state=117) 

网格搜索

找到最佳参数组合的常用方法是使用GridSearchCV。 不幸的是,据我所知,没有直接的方法可以将它与没有“预测”方法的模型一起使用。 (虽然这也不是不可能,但它需要更深入的面向对象设计,如果您有兴趣可以在网上找到。)

取而代之的是,我们可以使用方便的类 ParameterGrid 来创建每个参数组合。为了更好地看到它的实用性,我添加了另一个参数来优化:weights。 (见KNNImputer documentation

# Designing the parameters grid
param_grid = ParameterGrid({"n_neighbors": list(range(1, 15)),
     "weights": ["uniform", "distance"]})
# > [{'n_neighbors': 1, 'weights': 'uniform'}, {'n_neighbors': 1, 'weights': 'distance'}, 
#    {'n_neighbors': 2, 'weights': 'uniform'}, {'n_neighbors': 2, 'weights': 'distance'}, 
# and so on ...]

得分

这里没什么特别的。我们将保留您的(固定)操作,这在很大程度上可能被称为“绝对误差总和”。

# Our custom scoring, as a function.
# Here a lambda function, because it is a quite simple one.
aes = lambda y_true, y_pred: np.abs(y_true-y_pred).sum().sum()

培训

最后,我们可以在循环中进行。这基本上是 sklearn 的网格搜索的工作原理,只是它使用交叉验证而不是单个训练测试拆分。

# Let's train our models.
# We will keep the parameter and score of every combination.
results = []
for param_combination in param_grid:
    imputer = KNNImputer(**param_combination)
    train_pred = imputer.fit_transform(X_train)
    score = aes(y_train, train_pred)
    param_combination["train_score"]= score
    param_combination["val_score"] = aes(y_val, imputer.transform(X_val))
    results.append(param_combination)

print(results)
# > [{'n_neighbors': 1, 'weights': 'uniform', 'train_score': 41.0, 'val_score': 13.0},
#    {'n_neighbors': 1, 'weights': 'distance', 'train_score': 41.0, 'val_score': 13.0},
#    {'n_neighbors': 2, 'weights': 'uniform', 'train_score': 24.0, 'val_score': 18.0},
#    {'n_neighbors': 2, 'weights': 'distance', 'train_score': 24.682170521568423, 'val_score': 17.91644578836907},
#    and so on ...]

我们将结果存储为 dict 列表,以便我们可以轻松地将它们作为 Pandas DataFrame 获取。

results_df = pd.DataFrame(results)
results_df
# > n_neighbors     weights     train_score     val_score
# 0     1           uniform     32.000000       16.000000
# 1     1           distance    32.000000       16.000000
# 2     2           uniform     40.000000       16.500000
# 3     2           distance    39.451212       15.841781
# and so on ...

results_df.sort_values("val_score")
# > n_neighbors     weights     train_score     val_score
# 10    6           uniform     36.000000       8.666667
# 11    6           distance    35.024333       8.986608
# 24    13          uniform     32.000000       9.111111
# 22    12          uniform     32.000000       9.111111
# and so on ...

我们现在完成了。我们知道最佳参数集,但请记住,不同的抽签结果可能会有很大差异。

走得更远

避免过度拟合

Cross validation 是一种确保模型性能不受特殊训练数据限制的方法。这种现象被称为overfitting。 像 KNN 这样的模型就是这种情况,它通常会在 k 值较低的情况下过度拟合。

获得最佳估算器

我们本可以在训练循环内将迄今为止最好的估计器保存在内存中。

results = []
best_estimator, best_params, best_val_score = None, None, np.inf
for param_combination in param_grid:
    imputer = KNNImputer(**param_combination)
    train_pred = imputer.fit_transform(X_train)
    score = aes(y_train, train_pred)
    val_score = aes(y_val, imputer.transform(X_val))
    param_combination["train_score"]= score
    param_combination["val_score"] = val_score
    results.append(param_combination)
    if val_score < best_val_score:
       best_estimator = imputer
       best_params = param_combination
       best_val_score = val_score

# Now we can use directly the best estimator
print(best_params)
# > {'n_neighbors': 6,
#    'weights': 'uniform',
#    'train_score': 36.000000,
#    'val_score': 8.666667}
some_other_pred = best_estimator.transform(some_other_data)
相关问题