公司动态
Airbnb房价数据清洗与特征工程实战:从缺失值处理到批量自动化
这次我们直接看一个数据类项目里最常见的硬骨头Airbnb 房源数据的清洗与整形。数据科学流程中模型训练之前最耗时间的往往不是调参而是把原始表处理成干净、稳定、可供模型直接使用的特征集。这篇文章围绕 Airbnb Listings 数据完整演示数据清洗、数据整形、特征工程和批量处理的思路并顺带解决一个非常容易出现的问题目录结构没规划好启动批量任务时报cannot mkdir structure之类的错误。先把这个项目能做什么、值得关注的点说清楚。面向 Airbnb 房源数据做字段级清洗缺失值、重复值、异常值、类型转换。做数据整形价格标准化、日期解析、分类变量编码、数值特征缩放。做特征工程从原始字段构建新特征方便后续建模或分析。提供批量处理方案通过统一目录结构和 Python 脚本对多份数据重复执行清洗流程。强调可复现性环境、命令、代码全部给出按步骤就能跑通。如果你正在写数据分析类毕业设计、参加数据竞赛或者在业务中处理类似的结构化房源数据这篇文章建议直接收藏。接下来先看整体能力规划再按步骤操作。1. 核心能力速览能力项说明项目类型数据清洗与特征工程实战数据类型Airbnb 房源列表结构化表格数据核心字段id、name、host_id、room_type、price、minimum_nights、number_of_reviews、last_review、availability_365 等主要工具Python、pandas、NumPy、scikit-learn、pathlib显存要求无纯 CPU 环境即可运行启动方式命令行 Jupyter Notebook / VSCode输入数据CSV 格式房源数据单文件或批量目录输出结果清洗后的 CSV、特征矩阵、数据质量报告是否支持批量任务支持通过脚本遍历数据目录API 接口不涉及以脚本和本地文件为主适合场景数据分析、数据竞赛、Airbnb 数据可视化、特征工程练习从表格能看到这个项目的硬件门槛几乎为零。不需要 GPU不需要大显存本质上是结构化数据的 DataFrame 处理流程。重点考验的反而是 Python 编码习惯和数据处理流程的设计。2. 适用场景与使用边界Airbnb 房源数据清洗整形最适合以下几类读者。第一类是正在做数据分析入门练习的人。Inside Airbnb 这类开放数据源提供了全球多个城市的房源列表字段覆盖房价、房型、评论数、房东信息等天然适合练手。第二类是准备数据竞赛的人。很多比赛会提供多张表比如 listings、calendar、reviews。能不能把 listings 表清洗干净直接决定后续 join 和特征工程的效果。第三类是业务侧的数据分析人员。比如酒店、短租、房地产相关业务可能拿到的内部数据结构和 Airbnb 很接近。这套清洗流程可以复用。使用边界也需要明确。Airbnb 数据涉及真实地理坐标、房东信息、房源名称使用时要遵守数据源的许可协议只用于学习或研究不要做商业用途不要暴露个人隐私。涉及真实用户数据的场景必须先做脱敏处理再进入清洗流程。数据清洗本身也存在技术边界。清洗不是越狠越好删掉过多缺失行可能导致数据分布偏离填充缺失值如果策略不当也会引入偏差。每一步操作都要记录保证可回溯。3. 环境准备与数据集加载3.1 基础环境推荐用 conda 或 venv 创建独立 Python 环境避免把系统 Python 弄乱。python -m venv airbnb_env激活环境Windows:airbnb_env\Scripts\activateLinux / macOS:source airbnb_env/bin/activate然后安装依赖。pip install pandas numpy scikit-learn matplotlib seaborn jupyter版本方面pandas 建议 2.xPython 建议 3.9 以上。如果你的电脑里已经装了 Anaconda直接新建一个 conda 环境也可以。3.2 数据集字段Airbnb 房源列表数据常见字段如下表。字段含义清洗重点id房源唯一标识重复值检查name房源名称缺失填充、文本清理host_id房东唯一标识重复检查host_name房东名称缺失填充neighborhood_group区域组缺失值处理neighborhood具体区域缺失值处理latitude纬度区间校验longitude经度区间校验room_type房型分类编码price价格字符串转数值minimum_nights最少住宿晚数异常值处理number_of_reviews评论数异常值处理last_review最近评论日期日期解析reviews_per_month月评论数缺失值填充calculated_host_listings_count房东房源数类型转换availability_365全年可订天数区间校验这些字段基本覆盖了清洗的主要类型文本、数字、日期、分类、地理坐标。3.3 加载数据先用 pandas 读取原始 CSV。import pandas as pd import numpy as np df pd.read_csv(data/raw/airbnb_listings.csv) print(原始数据形状:, df.shape) print(\n字段列表:) print(df.columns.tolist()) print(\n字段类型和缺失情况:) print(df.info())输出重点看三样东西总行数和总列数。各字段的数据类型是否正确。哪些字段有缺失值。如果列名有前后空格或特殊符号建议先统一处理df.columns df.columns.str.strip().str.lower().str.replace( , _)这样后续所有代码都用统一的小写带下划线列名避免因为price和Price不对应导致报错。4. 目录结构规划与 mkdir 错误排查数据清洗最容易翻车的往往不是清洗逻辑本身而是项目目录没规划好。尤其是批量任务目录一乱到处报错。很多人在跑批量任务时会遇到这样的报错mkdir: cannot mkdir structure meed cleaning这个报错的本质是mkdir创建目录时出了问题。常见原因有三种。盘符下没有对应路径直接创建多级嵌套目录失败。mkdir参数写成了带空格的多级目录名系统把它当成单个目录名处理。目标路径已经存在同名目录但操作脚本没有做存在性判断。Windows 下的 PowerShell 或 CMD 中直接输入mkdir data/processed可能受当前目录影响路径解析和预期不符。更好的做法是用 Python 的pathlib统一管理目录。from pathlib import Path PROJECT_ROOT Path(__file__).resolve().parent RAW_DIR PROJECT_ROOT / data / raw PROCESSED_DIR PROJECT_ROOT / data / processed REPORT_DIR PROJECT_ROOT / data / reports LOG_DIR PROJECT_ROOT / logs for d in [RAW_DIR, PROCESSED_DIR, REPORT_DIR, LOG_DIR]: d.mkdir(parentsTrue, exist_okTrue)核心是三点parentsTrue递归创建多级目录只要上级不存在就一并创建。exist_okTrue目录已存在时不报错不会中断脚本。用Path对象而不是硬编码字符串拼接跨平台兼容更好。推荐的目录结构如下。airbnb_project/ ├── data/ │ ├── raw/ # 原始数据只读 │ ├── processed/ # 清洗后的中间数据 │ ├── curated/ # 最终特征集 │ └── reports/ # 数据质量报告 ├── logs/ # 运行日志 ├── scripts/ # 清洗脚本 ├── notebooks/ # 探索分析 Notebook └── outputs/ # 图表和结果目录结构规划完成之后再处理数据才不会出现“文件存在但读写路径不一致”“批量任务找不到输出目录”这类低级问题。5. Airbnb 原始数据清洗实战清洗阶段的目标是把原始数据变成“一致、完整、可计算”的表格。具体来说分为缺失值处理、重复值处理、异常值处理、类型转换四步。5.1 缺失值统计先量化缺失情况。missing df.isnull().sum() missing missing[missing 0].sort_values(ascendingFalse) print(missing)常见的缺失字段是last_review、reviews_per_month、host_name、neighborhood。处理策略last_review缺失如果number_of_reviews为 0说明没有评论日期缺失是合理的。reviews_per_month缺失同样与评论数有关可填充 0。host_name缺失占比很小可以用“Unknown”填充。neighborhood缺失如果neighborhood_group存在可以用区域组填充否则保留缺失并单独标记。示例代码df[reviews_per_month] df[reviews_per_month].fillna(0) df[host_name] df[host_name].fillna(Unknown) df[last_review] pd.to_datetime(df[last_review], errorscoerce) # 评论数为0但日期缺失保持NaN评论数0但日期缺失标记为异常 df[last_review_missing_anomaly] ( (df[number_of_reviews] 0) (df[last_review].isna()) ).astype(int)缺失值处理不是机械填充而是要根据业务含义选择填充值。5.2 重复值处理先用id列查重。dup_count df.duplicated(subset[id], keepFalse).sum() print(id 重复数量:, dup_count) # 查看重复项 dup_df df[df.duplicated(subset[id], keepFalse)].sort_values(id) print(dup_df.head())如果id重复但其他字段不同需要先看差异。可能是同一个房源的更新记录也可能是数据采集错误。简单场景保留第一条即可df df.drop_duplicates(subset[id], keepfirst)5.3 异常值处理房源的price、minimum_nights、availability_365都可能存在异常值。比如价格是 0、负数、或者超过 10000 美元最少住宿晚数超过 365经纬度不在合理范围。# 价格合理范围检查 print(价格为0或负数的数量:, (df[price_clean] 0).sum()) print(价格超过1000的数量:, (df[price_clean] 1000).sum()) # 经纬度范围检查 valid_lat df[latitude].between(-90, 90) valid_lon df[longitude].between(-180, 180) # 晚数检查 valid_nights df[minimum_nights].between(1, 365) df df[valid_lat valid_lon valid_nights]注意异常值删除要结合业务判断。价格 2000 美元一晚在纽约可能真实存在但价格 10000 美元一晚大概率是数据录入错误。更稳妥的方式是先做分位数统计再决定阈值。price_q99 df[price_clean].quantile(0.99) print(价格99分位数:, price_q99)5.4 数据类型转换Airbnb 数据最常见的类型问题是price字段带$符号和逗号pandas 默认读成字符串。df[price_clean] ( df[price] .replace([\$,], , regexTrue) .astype(float) )last_review用pd.to_datetime解析df[last_review] pd.to_datetime(df[last_review], errorscoerce)calculated_host_listings_count通常是整数df[calculated_host_listings_count] pd.to_numeric( df[calculated_host_listings_count], errorscoerce ).fillna(0).astype(int)数据类型统一之后后续聚合、分组、筛选都会快很多。6. 数据整形与特征工程实战清洗完成之后进入数据整形阶段。这一步的目标是让数据适合分析或建模。6.1 分类变量编码room_type是典型的分类变量取值包括 Entire home/apt、Private room、Shared room、Hotel room。建模前需要把它转换成数值格式。一个常用方式是factorizedf[room_type_code], _ pd.factorize(df[room_type]) print(df[[room_type, room_type_code]].drop_duplicates())也可以用 scikit-learn 的LabelEncoderfrom sklearn.preprocessing import LabelEncoder le LabelEncoder() df[room_type_label] le.fit_transform(df[room_type])neighborhood_group、neighborhood也可以用同样方式处理。但要注意neighborhood类别数量很多时标签编码会有排序含义不一定适合所有模型。这时候可以考虑 one-hot 编码或者只保留高频区域。6.2 日期特征衍生从last_review可以衍生出几个时间特征距今间隔天数。最近评论年份。最近评论月份。ref_date pd.Timestamp(2025-01-01) df[days_since_last_review] (ref_date - df[last_review]).dt.days df[last_review_year] df[last_review].dt.year df[last_review_month] df[last_review].dt.month这个处理思路对任何带日期的结构化数据都适用不只是 Airbnb。6.3 价格特征构建价格是 Airbnb 分析的核心。可以构建以下特征房价区间分箱。是否高价房源。人均价格如果字段里有 accommodates 或 guests 信息的话。# 价格分箱 bins [0, 50, 100, 200, 500, 5000] labels [economy, mid, premium, luxury, ultra] df[price_tier] pd.cut(df[price_clean], binsbins, labelslabels) # 是否高价房 df[is_high_price] (df[price_clean] 200).astype(int)这种特征对后续分类模型很有用。6.4 数值特征缩放如果需要训练基于距离的模型比如 KNN、SVM数值特征要先做标准化。from sklearn.preprocessing import StandardScaler num_cols [price_clean, minimum_nights, number_of_reviews, reviews_per_month, availability_365] scaler StandardScaler() df[num_cols] scaler.fit_transform(df[num_cols])注意标准化之后的数据是建模输入不是最终给人看的报表。如果需要同时保留原始值建议输出两张表一张是原始可读数据一张是标准化后的建模特征矩阵。6.5 整形后的输出最后把清洗整形后的数据保存为两份。# 可读数据 df.to_csv(PROCESSED_DIR / airbnb_listings_clean.csv, indexFalse) # 建模特征矩阵 feature_columns [price_clean, minimum_nights, number_of_reviews, reviews_per_month, availability_365, room_type_code, is_high_price, days_since_last_review] df[feature_columns].to_csv( PROCESSED_DIR / airbnb_features.csv, indexFalse )到这里一个完整的清洗整形流程就跑通了。7. 批量任务与自动化处理实际业务里通常不会只有一份 CSV。可能有纽约、伦敦、东京等多个城市的数据每个月更新一次。这时候就需要批量任务。7.1 目录遍历用glob或Path.glob遍历所有原始 CSVimport glob raw_files list(Path(RAW_DIR).glob(*.csv)) print(找到原始文件:, len(raw_files))7.2 批量清洗脚本把前面的清洗流程封装成函数。def clean_airbnb(df: pd.DataFrame) - pd.DataFrame: df df.copy() df.columns df.columns.str.strip().str.lower().str.replace( , _) # 缺失值 df[reviews_per_month] df[reviews_per_month].fillna(0) df[host_name] df[host_name].fillna(Unknown) # 类型转换 df[price_clean] df[price].replace([\$,], , regexTrue).astype(float) df[last_review] pd.to_datetime(df[last_review], errorscoerce) # 重复值 df df.drop_duplicates(subset[id], keepfirst) # 区间校验 df df[df[price_clean] 0] df df[df[availability_365].between(0, 365)] return df def process_file(file_path: Path, output_dir: Path): df pd.read_csv(file_path) df_clean clean_airbnb(df) output_path output_dir / f{file_path.stem}_clean.csv df_clean.to_csv(output_path, indexFalse) print(f处理完成: {file_path.name} - {output_path.name}) # 保存数据质量摘要 summary { file: file_path.name, original_rows: len(df), clean_rows: len(df_clean), dropped_rows: len(df) - len(df_clean), } return summary7.3 批量执行summaries [] for raw_file in RAW_DIR.glob(*.csv): summary process_file(raw_file, PROCESSED_DIR) summaries.append(summary) summary_df pd.DataFrame(summaries) summary_df.to_csv(REPORT_DIR / batch_summary.csv, indexFalse) print(summary_df)这样所有城市的房源数据会按照统一规则清洗并输出一份处理报告。7.4 日志与失败重试批量任务要加日志记录每个文件的处理状态。建议用 Python 的logging模块而不是简单print。import logging logging.basicConfig( filenamestr(LOG_DIR / airbnb_clean.log), levellogging.INFO, format%(asctime)s [%(levelname)s] %(message)s, ) for raw_file in RAW_DIR.glob(*.csv): try: result process_file(raw_file, PROCESSED_DIR) logging.info(fOK {raw_file.name} rows{result[clean_rows]}) except Exception as e: logging.error(fFAIL {raw_file.name} error{str(e)})日志文件一旦生成后续排查问题会轻松很多。8. 资源占用与性能观察这个项目不涉及 GPU资源占用的重点在 CPU 和内存。8.1 内存占用df.info()输出的memory usage可以直接看到内存占用。如果数据量大可以先用nrows参数抽样测试df_sample pd.read_csv(data/raw/airbnb_listings.csv, nrows10000)跑通之后再全量加载。8.2 性能优化如果数据量达到几百万行建议做三件事用read_csv的dtype参数指定字段类型避免默认把所有列读成 object。只读取需要的列用usecols参数。处理完及时释放内存用del df或参考gc.collect()。示例dtype_mapping { id: np.int64, host_id: np.int64, price: str, minimum_nights: np.int16, } df pd.read_csv( data/raw/airbnb_listings.csv, dtypedtype_mapping, usecols[id, host_id, price, minimum_nights, room_type, number_of_reviews], )8.3 观察方法清洗前后对比是最直观的验证方式。print(清洗前:, df_raw.shape) print(清洗后:, df_clean.shape)再看缺失值数量是否归零、重复数据是否去除、价格列是否为数值类型。整个流程不需要看显存重点看内存和耗时。9. 常见问题与排查方法问题现象可能原因排查方式解决方案cannot mkdir structure meed cleaning目录路径没有先创建父目录或 mkdir 参数理解错误查看脚本中路径定义检查当前工作目录用Path.mkdir(parentsTrue, exist_okTrue)创建目录FileNotFoundError: No such file or directory文件路径写错或相对路径基于错误的当前目录打印Path.cwd()和文件绝对路径改用Path(__file__).resolve().parent定位项目根目录ValueError: could not convert string to float字符串列中存在特殊字符或空值查看列的unique()或value_counts()先替换特殊字符再处理空值最后转类型重复数据清除后行数明显减少房源数据本身存在多货源更新记录检查id列的重复分布确认保留策略保留最新一条或任一条日期字段全是 NaT日期列格式不是标准 ISO 格式查看原始日期列内容指定format参数或者先统一字符串格式批量任务部分文件失败不同文件字段结构不一致用一个文件做 schema check统一列名和类型约束再加异常捕获输出 CSV 中文乱码编码问题检查原始 CSV 编码read_csv(encodingutf-8)或encodinggbk替代尝试价格列出现负数原始数据含错误录入或缺失标记查看价格字段的describe()按业务规则清洗设置合理范围这些问题是数据处理项目里出现频率最高的几类。遇到报错时第一步永远不是改代码而是先看清楚错误信息里的文件路径、行号、字段名再判断是哪一层问题。10. 最佳实践与数据合规建议最后给一组工程化建议做数据清洗任务时直接套用。第一第一次跑通小样本。先用nrows5000验证全流程再全量运行。这样错误能快速暴露不用等几分钟才发现逻辑问题。第二保留原始数据只读。data/raw目录下的原始文件不要修改所有清洗结果写入processed和curated目录。这样随时可以回溯。第三每一步操作都留痕。建议在中间步骤输出 CSV或者至少打印变化量。比如缺失值填充了多少行、删除了多少异常值。处理逻辑和结果可解释是数据工作能否被信任的关键。第四区分中间数据与最终特征集。清洗后的数据应该保留完整可读字段特征工程后的数据才用于建模。不要把两者混在一张表里。第五合规使用数据。Airbnb 数据来源于开放社区或第三方数据平台使用时务必确认许可协议。涉及真实房东名称、姓名、坐标位置的信息只用于学习研究不能用于商业用途。如果数据包含用户隐私字段需要先做脱敏处理再进入清洗流程。第六输出结果要复核。清洗完不等于做完。随机抽取几百行人工检查价格、房型、日期字段是否符合预期再进入下游分析。11. 总结Airbnb 房源数据清洗与整形的完整链路核心链路很清晰加载原始数据检查缺失值和重复值清理异常价格和地理坐标转换日期和分类字段最后构建价格分箱等特征输出可读数据和建模特征矩阵。整个项目不依赖 GPU普通 CPU 电脑就能跑通重点在于 pandas 和 Python 文件路径处理是否熟练。最容易踩的坑就是目录结构没规划好导致批量清洗时出现 cannot mkdir structure 之类的路径错误。建议第一次动手时先建好raw、processed、curated、logs四层目录用pathlib管理路径再把清洗脚本封装成函数。这样换城市、换月份的数据只需要换输入文件流程不用重写。后续可以继续扩展的方向有很多接入更完整的 Airbnb calendar 表做时间序列分析把文本描述字段做 NLP 处理或者把清洗后的特征集直接输入到 lightgbm 做价格预测。先把基础清洗流程跑稳后面的路就好走了。