
拿到一份 Airbnb Listings 数据很多人的第一反应是直接开始画图、跑模型结果一做特征分析就发现价格列带着$符号、评分大量缺失、同一套房源重复出现好几遍。本文就围绕 Airbnb 房源列表数据的清洗与整形整理一套从原始 CSV 到可直接分析的数据集完整流程包含代码示例、常见报错和工程习惯适合正在学习 pandas、准备做数据比赛或搭建数据分析项目的开发者。为了让过程可复现我会按“先体检、再清洗、后整形、再导出”的顺序讲解。你不需要额外准备数据库只要能跑 Python 和 pandas 就能跟着操作。1. 为什么要清洗和整形 Airbnb 房源数据1.1 什么是 Airbnb Listings 数据Airbnb Listings 数据简单说就是 Airbnb 平台上房源列表信息的汇总表。每条记录对应一套房源字段通常包含房源 ID、房源名称、房东信息、位置坐标、房间类型、可住人数、价格、评分、评论数、可预订天数等。这类数据在数据分析、城市研究、旅游推荐、价格预测等场景中非常常见。很多人会从公开渠道下载某个城市或区域的listings.csv文件然后基于它做分析。但公开数据并不等于“干净数据”恰恰相反它保留了真实业务数据里最常见的脏乱形态。1.2 原始数据常见问题把一份典型的 Airbnb listings 文件打开后你会遇到下面这些问题价格列是字符串例如$120.00带货币符号和千分位逗号不能直接求均值。浴室数量列是2.5 baths这类混合文本需要提取数字。last_review日期列可能是空值也可能是不同格式的日期字符串。host_is_superhost列是t/f这样的布尔文本不是 Python 的True/False。同一房源 ID 重复出现可能来自多次抓取或数据合并。大量列存在缺失值例如reviews_per_month对于没有评论的房源是空值。部分极端值例如价格为 0、可住人数为 0会影响统计结果。这些问题如果不处理后面的分组统计、透视表、可视化、机器学习建模都会出现偏差。1.3 清洗与整形的关系这里需要区分两个概念清洗Cleaning解决“数据不对”的问题包括缺失值、重复值、异常值、类型错误、格式不统一。整形Shaping解决“数据不方便用”的问题包括行列调整、字段拆分、类型转换、新增特征、分组汇总、透视转换。清洗是整形的前提整形是清洗后的下一步。大多数 pandas 教程会把它们混在一起讲但实际项目里最好按这个顺序推进先保证每条记录和每个字段是“对的”再把它变成“好用的”。本文的实战部分也采用这个流程。2. 环境准备与数据集说明2.1 运行环境与依赖本文代码基于 Python 3 环境核心依赖是 pandas 和 numpy。你可以用 pip 安装pip install pandas numpy版本需要根据你的项目实际情况调整本文示例以 pandas 1.5 / 2.x 的常见写法为例重点演示清洗与整形思路。如果你使用的是更早版本个别方法名可能有差异但整体流程一致。推荐使用 Jupyter Notebook 或 VS Code 的交互式窗口逐步执行方便分步查看每一阶段的数据形态。2.2 数据集字段说明为了演示效果下面以一份典型的 Airbnblistings.csv为例。真实数据可能列名略有不同但核心字段通常是这些字段名含义常见脏数据示例id房源唯一 ID重复记录name房源名称空白、乱码host_id房东 ID无host_name房东名称缺失neighbourhood_group区域组大小写混用latitude / longitude经纬度缺失或越界room_type房间类型空格、大小写不一致price每晚价格$120.00minimum_nights最少入住天数异常值 0number_of_reviews评论数缺失last_review最近评论日期字符串日期reviews_per_month每月评论数缺失availability_365一年可订天数缺失bathrooms_text浴室描述2.5 bathshost_is_superhost是否超赞房东t/f2.3 项目目录结构设计建议先建好目录把数据文件和脚本分离。这里有一个常见教训很多人把脚本、原始数据、输出结果全放在同一层目录开发到后面目录越来越乱甚至在不同路径之间复制文件时出现奇怪报错。一个朴素的目录结构如下airbnb_clean/ ├── data/ │ └── listings.csv ├── output/ │ └── airbnb_listings_clean.csv ├── scripts/ │ └── clean_listings.py └── notebooks/ └── 01_eda.ipynb在终端里创建目录时如果父目录不存在或权限不足会出现类似cannot mkdir的报错。这往往不是命令本身写错而是目录结构需要先理清要么提前创建好父目录要么检查当前用户是否有写权限。在 Python 脚本里更推荐使用pathlib的方式后面实战代码会给出。3. 核心方法拆解3.1 数据体检info、describe、dtypes拿到数据第一步不是清洗而是体检。pandas 提供了三个最基础的方法import pandas as pd df pd.read_csv(data/listings.csv) print(df.shape)df.shape查看行数和列数。df.info()查看每列的非空数量、数据类型是最快的缺失值扫描方式。df.describe(includeall)查看数值列和类别列的大致分布。执行完这三步你基本能判断哪些列需要重点处理。3.2 缺失值处理缺失值处理没有银弹核心思路是分列处理如果某列缺失比例过高且业务上不重要可以直接删除该列。如果缺失比例低可以选择填充常用中位数、均值、众数或固定值。如果缺失列是日期、评论等强相关字段可以结合业务规则填充例如没有评论的房源reviews_per_month填 0。判断缺失比例可以这样写missing df.isna().mean().sort_values(ascendingFalse) print(missing[missing 0])isna()返回布尔表mean()计算每列缺失比例按降序输出后一眼就能看出哪些列是“缺失大户”。3.3 重复值处理重复值要看业务含义完全重复的行可以直接删除。更常见的是id重复但其他字段略有差异这时需要指定subset参数按 ID 去重。df.drop_duplicates(subset[id], keepfirst, inplaceTrue)keepfirst表示保留第一次出现的记录后面的重复记录删除。3.4 数据类型修正pandas 读入 CSV 后所有列默认按内容推断类型。遇到混合类型时经常出现明明应该是数字的列变成object。修正方式是用pd.to_numericdf[price] pd.to_numeric(df[price], errorscoerce)errorscoerce的意思是能转成数字就转不能转的变成NaN。这样既不会因为个别脏数据中断程序也能在后续检查中看到哪些值有问题。3.5 字符串清洗字符串清洗通常处理四类问题去除首尾空格str.strip()统一大小写str.lower()/str.upper()替换或删除字符str.replace()提取子串str.extract()对于$120.00这样的价格可以先把货币符号和逗号去掉再转数值。对于2.5 baths可以用正则表达式提取小数部分。3.6 日期处理日期字段最常见的两个问题一是空值二是格式不统一。pandas 的pd.to_datetime能自动解析大部分常用日期格式df[last_review] pd.to_datetime(df[last_review], errorscoerce)一旦解析失败的值变成NaT日期列就是真正的datetime64类型后续可以做月份提取、天数计算等操作。4. 完整实战清洗并整形 Airbnb 房源表这一节我们把前面拆解的方法串成一个完整流程。建议新建一个脚本文件scripts/clean_listings.py每一段代码都可以独立运行也可以通过主函数串起来。4.1 创建项目目录并读取数据# 文件路径scripts/clean_listings.py from pathlib import Path import pandas as pd import numpy as np # 创建输出目录 BASE_DIR Path(__file__).resolve().parent.parent DATA_DIR BASE_DIR / data OUTPUT_DIR BASE_DIR / output OUTPUT_DIR.mkdir(parentsTrue, exist_okTrue) print(输出目录, OUTPUT_DIR)这里用exist_okTrue即使目录已经存在也不会报错。相比os.makedirs在目录已存在时抛FileExistsError这种方式更省心也避免了 shell 里cannot mkdir这类因目录冲突导致的报错。读取数据df pd.read_csv( DATA_DIR / listings.csv, na_values[, N/A, NA], ) print(原始数据形状, df.shape)na_values可以把常见空字符串统一识别为缺失值避免后续清洗时漏掉。4.2 第一步数据体检print(df.info())运行后会看到类似下面的输出列数会根据真实数据变化RangeIndex: 5000 entries, 0 to 4999 Data columns (total 18 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 5000 non-null int64 1 name 4930 non-null object 2 price 4970 non-null object 3 room_type 5000 non-null object 4 bathrooms_text 4800 non-null object ...可以明显看到price是object而不是数值name、bathrooms_text都有缺失。接下来查看数值分布和缺失比例print(df.describe(includeall)) missing_ratio df.isna().mean().sort_values(ascendingFalse) print(missing_ratio[missing_ratio 0])4.3 第二步处理缺失值这一步的策略是先删再填。先删除缺失严重且业务价值不高的列# 这些列如果缺失率过高直接删除 cols_to_drop [license, neighborhood_overview] df df.drop(columns[c for c in cols_to_drop if c in df.columns])对缺失率较低但有明确业务含义的列做填充# 没有评论的房源每月评论数填 0 df[reviews_per_month] df[reviews_per_month].fillna(0) # 评分缺失用中位数填充避免极端值影响 df[review_scores_rating] ( df[review_scores_rating].fillna(df[review_scores_rating].median()) ) # 房间描述缺失可以先填 Unknown 或暂时保留 df[name] df[name].fillna(Unknown)对于last_review日期列如果没有最近评论实际上不应该随意填一个假日期。更合理的做法是把日期解析后结合number_of_reviews判断评论数为 0 的房源把last_review保留为缺失在后续分析中单独处理即可。df[last_review] pd.to_datetime(df[last_review], errorscoerce)4.4 第三步处理重复值先统计完全重复的行print(完全重复行数, df.duplicated().sum()) df df.drop_duplicates()再看id层面的重复print(按 id 去重前的行数, len(df)) df df.drop_duplicates(subset[id], keepfirst) print(按 id 去重后的行数, len(df))很多真实场景里完全重复的行并不多但id重复很常见。按id去重后数据集的唯一性就有了保障。4.5 第四步修正字段类型与格式价格列清洗df[price] ( df[price] .astype(str) .str.replace($, , regexFalse) .str.replace(,, , regexFalse) .str.strip() ) df[price] pd.to_numeric(df[price], errorscoerce) print(df[price].describe())这里先把价格统一转成字符串再清洗避免个别行是数字时直接调用str方法报错。regexFalse表示把$当作普通字符替换不做正则匹配。浴室数量提取df[bathrooms] ( df[bathrooms_text] .astype(str) .str.extract(r([\d.])) .astype(float) ) df[bathrooms] pd.to_numeric(df[bathrooms], errorscoerce)str.extract(r([\d.]))会从2.5 baths中提取2.5从1 bath中提取1。布尔字段转换df[host_is_superhost] df[host_is_superhost].map( {t: True, f: False, T: True, F: False} )如果转换后仍有NaN说明原始数据里存在其他值可以打印检查print(df[host_is_superhost].value_counts(dropnaFalse))房间类型统一小写并去除空格df[room_type] ( df[room_type].astype(str).str.strip().str.lower() ) print(df[room_type].value_counts())4.6 第五步整形与特征塑造当基础字段都干净之后就可以进入“整形”阶段。常见的整形操作包括筛选、排序、分组、透视和新增特征。先剔除明显异常的记录df df[ df[price].between(10, 2000) df[accommodates].between(1, 16) ].copy()copy()很重要它避免后续修改触发SettingWithCopyWarning这类 pandas 警告。新增“人均价格”特征df[price_per_person] ( df[price] / df[accommodates].replace(0, np.nan) )把accommodates为 0 的情况先替换成NaN避免除零错误。新建价格区间列bins [0, 100, 200, 500, 10000] labels [经济型, 舒适型, 高端型, 豪华型] df[price_level] pd.cut( df[price], binsbins, labelslabels, rightFalse, )分组统计不同房型的房源数量、均价和评分room_summary ( df.groupby(room_type, as_indexFalse) .agg( 房源数(id, count), 平均价格(price, mean), 中位价格(price, median), 平均评分(review_scores_rating, mean), ) .sort_values(房源数, ascendingFalse) ) print(room_summary)如果数据里有neighbourhood_group字段还可以做房型和区域的交叉透视pivot pd.pivot_table( df, indexneighbourhood_group, columnsroom_type, valuesprice, aggfuncmedian, ) print(pivot.head())透视表很适合观察“哪个区域的哪种房型贵”这类问题。4.7 第六步导出清洗结果清洗完成后的数据导出为新的 CSV 文件output_path OUTPUT_DIR / airbnb_listings_clean.csv df.to_csv(output_path, indexFalse, encodingutf-8-sig) print(已保存, output_path)encodingutf-8-sig是为了让 Excel 打开 CSV 时不会出现中文乱码是中文项目里的实用习惯。5. 常见问题与排查思路在清洗 Airbnb 数据时最容易遇到下面这些问题问题现象常见原因解决思路启动脚本提示cannot mkdir父目录不存在、权限不足或目录已存在使用Path.mkdir(parentsTrue, exist_okTrue)或先检查目录与权限pd.read_csv报ParserError文件编码不对、CSV 引号不闭合指定encodingutf-8或enginepython价格列转数值后出现大量NaN清洗规则没覆盖特殊格式例如$1,200.00的去逗号顺序不对先用str.replace去掉$和,再pd.to_numericstr.extract提取不到数字源数据格式与正则不匹配先value_counts()查看唯一值再调整正则SettingWithCopyWarning警告对切片后的 DataFrame 直接赋值在切片后调用.copy()日期解析后全是NaT日期字符串格式特殊或混入文本用errorscoerce保留可解析值再单独检查无法解析的样本删除缺失列后行列数对不上列名不存在却被drop用列表推导式[c for c in cols if c in df.columns]过滤排查思路建议按“数据体检 → 定位影响范围 → 先处理结构问题再处理内容问题 → 每步打印形状确认”的顺序进行。每次清洗操作后都打印一次df.shape能很快定位是哪一步改变了行数或列数。6. 最佳实践与工程建议6.1 保留原始数据清洗数据单独输出永远不要把清洗结果覆盖到原始 CSV 上。原始文件是数据资产的基线清洗脚本应该可以重复执行输出到独立目录。这样即使清洗规则写错了重新跑一遍脚本即可。6.2 清洗脚本要可复现把清洗过程写成函数而不是在 Notebook 里一个 Cell 一个 Cell 地敲。推荐拆成def load_data(path): ... def clean_missing(df): ... def clean_types(df): ... def shape_features(df): ... def main(): df load_data(...) df clean_missing(df) df clean_types(df) df shape_features(df) ...每个函数只做一件事方便单独测试和复用。6.3 用日志记录每一步项目规模变大后清洗步骤可能非常多。建议在关键节点打印或记录日志import logging logging.basicConfig(levellogging.INFO) logging.info(after load: %s, df.shape) logging.info(after dedup: %s, df.shape)这样跑完整个脚本你能清楚看到每一次操作的影响。6.4 谨慎处理缺失值填充缺失值不等于凭空造数据。填充前要问自己这个缺失是“业务上不存在”还是“数据采集失败”业务上不存在例如没有评论的房源reviews_per_month填 0 合理。数据采集失败不应该直接填均值建议先分析缺失原因。6.5 正则和类型转换要兜底errorscoerce不是偷懒而是让程序在遇到脏数据时继续运行并在后续检查中暴露问题。关键是转换后要抽样检查NaN确认没有把有效数据误判成缺失。6.6 大批量数据时注意内存Airbnb 城市级别的数据通常只有几万行内存压力不大。但如果你处理的是全国或全球级别的数据建议在读取时只选择需要的列cols [id, price, room_type, accommodates, number_of_reviews, review_scores_rating, last_review] df pd.read_csv(data/listings.csv, usecolscols)7. 总结与下一步通过本文的流程你应该已经掌握了 Airbnb Listings 数据清洗与整形的完整方法先识别数据问题再处理缺失值、重复值和类型错误最后通过分组、透视、特征工程把数据变成适合分析和建模的形态。这套流程不仅适用于 Airbnb 数据换到商品列表、用户画像、订单流水等场景思路完全一致。接下来你可以继续学习三块内容可视化分析用 matplotlib 或 seaborn 查看不同房型、不同区域的价格分布。异常值检测用 IQR 或机器学习方法识别价格、评论数中的异常值。价格预测建模把清洗后的数据应用到线性回归或树模型预测房源价格。实际项目中优先关注缺失值处理和类型一致性这两步直接影响后续所有分析的准确性。建议你找一份真实的 Airbnb 数据把本文每个步骤都手动跑一遍遇到报错就对照常见问题表格排查很快就能把清洗流程建立成自己的标准模板。如果本文对你有帮助可以收藏备用也欢迎在评论区交流你在清洗 Airbnb 数据时遇到的其他坑。