📢 转载信息
原文链接:https://www.kdnuggets.com/the-data-detox-training-yourself-for-the-messy-noisy-real-world
原文作者:Nate Rosidi
在本文中,我们将利用一个真实的数据项目来探索为处理混乱、真实的现实世界数据集做准备的四个实用步骤。
# NoBroker数据项目:真实世界混乱的手动测试
NoBroker 是一家印度的房地产科技(prop-tech)公司,在一个无需中介的市场中直接连接房产所有者和租户。

这个数据项目用于NoBroker数据科学职位的招聘流程中。
在这个数据项目中,NoBroker希望你构建一个预测模型,以估算在给定时间段内,一个房产会收到多少次互动。我们不会在这里完成整个项目,但它将帮助我们发现处理混乱的真实世界数据的方法。
它包含三个数据集:
property_data_set.csv- 包含房产详情,如类型、位置、设施、面积、租金和其他住房特征。
property_photos.tsv- 包含房产照片。
property_interactions.csv- 包含房产互动的时间戳。
# 对比干净的面试数据与真实的生产数据:现实检验
面试数据集是经过精心打磨、平衡且无聊的。而真实的生产数据呢?它就像一个垃圾场,充满了缺失值、重复行、不一致的格式,以及那些总是在周五下午5点才跳出来破坏你数据管道的“静默错误”。
以NoBroker房产数据集为例,这是一个包含跨越三个表的28,888个房产的真实混乱数据。乍一看,它似乎没问题。但深入挖掘后,你会发现 11,022 个缺失的照片统一资源定位符(URL),包含流氓反斜杠的损坏JSON字符串,以及更多问题。
这就是干净与混乱之间的界限。干净的数据训练你构建模型,而生产数据则通过让你经历挣扎来训练你如何生存。
我们将探讨四种训练自己的方法。

# 实践 #1:处理缺失数据
缺失数据不仅仅是令人恼火的问题;它是一个决策点。是删除该行?用均值填充?还是标记为未知?答案取决于数据缺失的原因以及你可以承受多少数据丢失。
NoBroker 数据集有三种类型的缺失数据。在 28,888 行中,photo_urls 列缺失了 11,022 个值——这占了数据集的 38%。代码如下。
pics.isna().sum()
这是输出结果。

删除这些行将丢失宝贵的房产记录。相反,解决方案是将缺失的照片视为零张照片,然后继续处理。
def correction(x): if x is np.nan or x == 'NaN': return 0 # Missing photos = 0 photos else: return len(json.loads(x.replace('\', '').replace('{title','{"title'))) pics['photo_count'] = pics['photo_urls'].apply(correction)
对于像 total_floor (缺失23个) 和 building_type (缺失38个) 这样的数值列和分类列,策略是插补 (imputation)。用均值填充数值空缺,用众数填充分类空缺。
for col in x_remain_withNull.columns: x_remain[col] = x_remain_withNull[col].fillna(x_remain_withNull[col].mean()) for col in x_cat_withNull.columns: x_cat[col] = x_cat_withNull[col].fillna(x_cat_withNull[col].mode()[0])
第一个决策:不要在没有质疑精神的情况下删除数据!
理解模式。缺失的照片URL并非随机出现。
# 实践 #2:检测异常值
异常值不一定总是一个错误,但它总是值得怀疑的。
你能想象一间有 21 间浴室、800 年历史或 40,000 平方英尺空间的房产吗?你可能找到了梦想中的住所,或者有人犯了数据录入错误。
NoBroker 数据集中充满了这类危险信号。箱线图显示了多个列中的极端值:房龄超过 100 年、面积超过 10,000 平方英尺(sq ft),以及押金超过 350 万的房产。其中一些可能是合法的豪华房产。但大多数都是数据录入错误。
df_num.plot(kind='box', subplots=True, figsize=(22,10)) plt.show()
这是输出结果。

解决方案是基于四分位距 (IQR) 的异常值移除法,这是一种统计方法,它会标记出超出 2 倍 IQR 的值。
为了处理这个问题,我们首先编写一个函数来移除这些异常值。
def remove_outlier(df_in, col_name): q1 = df_in[col_name].quantile(0.25) q3 = df_in[col_name].quantile(0.75) iqr = q3 - q1 fence_low = q1 - 2 * iqr fence_high = q3 + 2 * iqr df_out = df_in.loc[(df_in[col_name] <= fence_high) & (df_in[col_name] >= fence_low)] return df_out # Note: Multiplier changed from 1.5 to 2 to match implementation.
然后我们对数值列运行此代码。
df = dataset.copy() for col in df_num.columns: if col in ['gym', 'lift', 'swimming_pool', 'request_day_within_3d', 'request_day_within_7d']: continue # Skip binary and target columns df = remove_outlier(df, col) print(f"Before: {dataset.shape[0]} rows") print(f"After: {df.shape[0]} rows") print(f"Removed: {dataset.shape[0] - df.shape[0]} rows ({((dataset.shape[0] - df.shape[0]) / dataset.shape[0] * 100):.1f}% reduction)")
这是输出结果。

移除异常值后,数据集从 17,386 行减少到 15,170 行,丢失了 12.7% 的数据,但保持了模型的合理性。这种权衡是值得的。
对于像 request_day_within_3d 这样的目标变量,我们使用了封顶 (capping) 而不是删除。大于 10 的值被封顶到 10,以防止极端异常值扭曲预测结果。在下面的代码中,我们还比较了封顶前后的结果。
def capping_for_3days(x): num = 10 return num if x > num else x df['request_day_within_3d_capping'] = df['request_day_within_3d'].apply(capping_for_3days) before_count = (df['request_day_within_3d'] > 10).sum() after_count = (df['request_day_within_3d_capping'] > 10).sum() total_rows = len(df) change_count = before_count - after_count percent_change = (change_count / total_rows) * 100 print(f"Before capping (>10): {before_count}") print(f"After capping (>10): {after_count}") print(f"Reduced by: {change_count} ({percent_change:.2f}% of total rows affected)")
结果如何?

一个更清晰的分布,更好的模型性能,以及更少的调试会话。
# 实践 #3:处理重复项和不一致性
重复项很容易处理。不一致性则很困难。重复行只需要使用 df.drop_duplicates()。而不一致的格式,比如被三个不同系统破坏的 JSON 字符串,则需要侦探般的工作。
NoBroker 数据集遇到了最糟糕的 JSON 不一致性之一。photo_urls 列本应包含有效的 JSON 数组,但相反,它充满了格式错误的字符串、缺失的引号、转义的反斜杠和随机的尾随字符。
text_before = pics['photo_urls'][0] print('Before Correction: \n\n', text_before)
这是校正前的样子。

修复需要多次字符串替换来纠正格式,然后才能解析。代码如下。
text_after = text_before.replace('\', '').replace('{title', '{"title').replace(']"', ']').replace('],"', ']","') parsed_json = json.loads(text_after)
这是输出结果。

修复后,JSON 确实变得有效且可解析。这不是进行此类字符串操作的最干净方法,但它有效。
你在各种地方都会看到不一致的格式:日期被保存为字符串,分类值中存在拼写错误,以及数值 ID 被保存为浮点数。
解决方案是标准化,就像我们对 JSON 格式所做的那样。
# 实践 #4:数据类型验证和模式检查
一切都始于加载数据时。如果后来才发现日期是字符串,或者数字是对象类型,那将是时间的浪费。
在NoBroker项目中,数据类型是在读取 CSV 的过程中验证的,因为该项目使用 pandas 参数强制数据类型先行。代码如下。
data = pd.read_csv('property_data_set.csv') print(data['activation_date'].dtype) data = pd.read_csv('property_data_set.csv', parse_dates=['activation_date'], infer_datetime_format=True, dayfirst=True) print(data['activation_date'].dtype)
这是输出结果。

相同的验证也应用于互动数据集。
interaction = pd.read_csv('property_interactions.csv', parse_dates=['request_date'], infer_datetime_format=True, dayfirst=True)
这不仅是良好的实践,对于任何下游操作都是至关重要的。该项目需要计算激活日期和请求日期之间的日期和时间差。
因此,如果日期是字符串,以下代码将产生错误。
num_req['request_day'] = (num_req['request_date'] - num_req['activation_date']) / np.timedelta64(1, 'D')
模式检查将确保结构不会改变,但实际上,随着数据分布随时间变化,数据也会发生漂移。你可以通过让输入比例略有变化来模拟这种漂移,并检查你的模型或其验证是否能够检测并响应这种漂移。
# 记录你的清理步骤
三个月后,你不会记得为什么将 request_day_within_3d 限制为 10。六个月后,你的队友可能会通过移除你的异常值过滤器来破坏管道。一年后,模型投入生产,但没有人会理解它为什么会失败。
文档记录不是可选项。这是可复现管道与“只在奏效时有效”的“巫术脚本”之间的区别。
NoBroker 项目将每一步转换都记录在代码注释和结构化的笔记本章节中,并附有解释和目录。
# Assignment # Read and Explore All Datasets # Data Engineering Handling Pics Data Number of Interactions Within 3 Days Number of Interactions Within 7 Days Merge Data # Exploratory Data Analysis and Processing # Feature Engineering Remove Outliers One-Hot Encoding MinMaxScaler Classical Machine Learning Predicting Interactions Within 3 Days Deep Learning # Try to correct the first Json # Try to replace corrupted values then convert to json # Function to correct corrupted json and get count of photos
版本控制也很重要。跟踪清洗逻辑的更改。保存中间数据集。记录你尝试过什么以及什么有效。
目标不是完美。目标是清晰。 如果你不能解释你做出决定的原因,那么当模型失败时,你就无法为它辩护。
# 最终思考
干净的数据是一个神话。最优秀的数据科学家不是那些逃避混乱数据集的人;而是那些知道如何驯服它们的人。他们在训练前发现缺失值。
他们在影响预测结果之前识别出异常值。他们在合并表之前检查模式。他们把所有事情都记录下来,这样下一个人就不必从零开始。
真正的价值不来自于完美的数据。它来自于处理错误数据并仍然能够构建出功能性系统的能力。
因此,当你不得不处理一个数据集并看到空值、损坏的字符串和异常值时,不要害怕。你所看到的不是问题,而是在一个真实世界的数据集面前展示你技能的机会。
Nate Rosidi 是一位数据科学家和产品策略师。他还是教授分析学的兼职教授,也是 StrataScratch 的创始人,该平台通过来自顶尖公司的真实面试问题帮助数据科学家准备面试。Nate 撰写有关职业市场最新趋势、提供面试建议、分享数据科学项目,并涵盖所有与 SQL 相关的内容。
🚀 想要体验更好更全面的AI调用?
欢迎使用青云聚合API,约为官网价格的十分之一,支持300+全球最新模型,以及全球各种生图生视频模型,无需翻墙高速稳定,文档丰富,小白也可以简单操作。
评论区