Pandas数据清洗与整形实战:Airbnb房源数据完整处理指南
之前处理 Airbnb 房源数据时,最耗时的往往不是建模,而是怎么把一份真实且凌乱的 listing 表弄干净。价格字段带着$和千分位逗号、last_review大量缺失、room_type出现Room/room/Private room多种写法,这些问题不提前处理,后面任何统计、可视化和建模都会跑偏。网上关于 pandas 数据清洗的教程很多,但多数只讲单个知识点,很难直接套到一份完整数据集上。本文将围绕 Cleaning and Shaping the Airbnb Listings 这一主题,整理一套从读取、探查、清洗、整形到导出的完整流程,并补充几个真实项目中常见的报错排查思路。无论你刚入门数据分析,还是准备用 Airbnb 公开数据做课程项目或 Kaggle 练习,都可以直接按这篇文章的步骤走一遍。
1. 背景与核心概念
1.1 什么是 Airbnb Listings 数据
Airbnb Listings 数据,简单理解就是房源列表信息。一份常见的房源表会包含房源编号、房源标题、房东信息、所在区域、房型、每晚价格、最少入住晚数、评论数量、最近评论日期、可订天数、经纬度等字段。如果从 Inside Airbnb 这类第三方公开数据平台下载,通常可以得到单个城市的listings.csv,文件大小从几 MB 到几十 MB 不等,行数从几千到几万都有可能。
这类数据最大的特点是“看起来能直接用,实际上一用就报错”。比如价格列是字符串"$120.00",评论数有时是数字有时是字符串,日期列有的写成2023-06-15,有的写成2022/11/03,甚至还有空值。所以拿到数据后的第一步,不是画图,不是建模,而是先做 data cleaning 和 data shaping。
1.2 数据清洗和数据整形有什么区别
在动手写代码之前,先厘清两个概念。
- 数据清洗(Data Cleaning):处理缺失值、重复值、异常值、错误格式,目标是让数据“可用”。
- 数据整形(Data Shaping):调整数据的列名、数据类型、结构、派生字段,目标是让数据“好用”。
清洗解决的是“脏不脏”的问题,整形解决的是“顺不顺手”的问题。实际项目中两者经常连续出现,清洗过程中会顺带做整形,整形过程中也可能发现新的清洗需求。比如把price从字符串转成浮点数,本质上既是清洗也是整形;把last_review统一成datetime类型,同样跨越了两个环节。
1.3 为什么这两步如此重要
真实数据分析项目中,数据清洗和整形通常要占整个项目 50% 到 70% 的时间。如果这一步做得不扎实,模型训练效果会非常差,甚至出现“垃圾进、垃圾出”的情况。反过来说,如果能把这一步标准化、函数化,以后每次拿到新的城市数据,只需要改路径和个别字段名,就能快速产出一份干净的分析底表,这也是本篇文章想帮你达到的目的。
2. 环境准备与版本说明
在开始代码实战前,先确认运行环境。
- 操作系统:Windows / macOS / Linux 都可以,以下命令与平台无关。
- Python:建议 3.9 或更高版本,3.7 以上大部分代码也能运行。
- pandas:建议 1.5 以上或 2.x 版本。版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路和清洗逻辑。
- IDE:推荐 Jupyter Notebook 或 VS Code,便于逐步观察中间结果。
在终端中安装依赖:
pip install pandas numpy如果你使用的 Anaconda 环境,pandas 通常已经内置,可以直接跳过这一步。为了不污染全局环境,也可以先创建虚拟环境:
python -m venv airbnb_env source airbnb_env/bin/activate # Windows 下使用 airbnb_env\Scripts\activate pip install pandas numpy建议的项目目录结构如下:
airbnb_analysis/ ├── data/ │ └── listings.csv ├── notebooks/ │ └── cleaning_shaping.ipynb ├── output/ └── scripts/ └── clean_listings.pydata目录放原始数据,output目录放清洗后的文件,脚本统一放在scripts下。养成数据目录和代码目录分离的习惯,可以避免把原始数据改乱。
3. 数据读取与初步探查
3.1 读取 CSV 文件
先用 pandas 把数据读进来。假设listings.csv位于data目录下:
import pandas as pd DATA_PATH = "data/listings.csv" df = pd.read_csv(DATA_PATH)这里有一个常用参数:如果你需要预览前几行,可以直接看head();如果文件很大,可以指定nrows=1000先加载前 1000 行做快速验证,避免每次读取全量数据等待时间太长。
df_sample = pd.read_csv(DATA_PATH, nrows=1000)3.2 查看数据概览
读取之后,先用几个基础命令建立对数据的整体印象。
print("数据形状:", df.shape) print("列名:", df.columns.tolist()) df.head()一个典型的输出可能是:
数据形状: (28732, 16) 列名: ['id', 'name', 'host_name', 'neighbourhood_group', 'room_type', 'price', 'minimum_nights', 'number_of_reviews', 'last_review', 'availability_365', 'latitude', 'longitude', ...]从shape可以知道有多少行、多少列。从列名能大概看出这份表包含哪些信息。此时建议继续用info()查看每一列的非空数量和数据类型:
df.info()info()输出里如果某列显示object,通常意味着它被 pandas 当成了文本,下一步要做类型转换。如果某列的非空数量明显小于总行数,说明存在缺失值。
3.3 缺失值与类型概览
快速查看每列缺失值数量:
missing = df.isna().sum() missing_percent = (df.isna().sum() / len(df) * 100).round(2) missing_df = pd.DataFrame({"缺失数量": missing, "缺失占比": missing_percent}) missing_df输出样式如下:
| 字段 | 缺失数量 | 缺失占比 |
|---|---|---|
| host_name | 12 | 0.04% |
| last_review | 3620 | 12.60% |
| price | 0 | 0.00% |
通过这步,可以快速圈出需要重点处理的列。一般来说,缺失占比小于 1% 的列可以直接填充或删除少量行;缺失占比超过 20% 的列需要认真思考业务含义;如果某列缺失 90% 以上,基本可以考虑整列丢弃。
4. 数据清洗
4.1 删除重复记录
Airbnb 数据在收集或合并过程中,可能出现重复行。先用duplicated()检查:
print("完全重复行数:", df.duplicated().sum())如果重复行数很多,用drop_duplicates()删除:
df = df.drop_duplicates().copy()但在真实数据里,“完全重复”其实不算常见,更常见的是部分字段重复,例如同一个id出现了两行,但其中一行的name或价格字段有出入。此时应该根据业务主键去重:
df = df.drop_duplicates(subset=["id"], keep="first").copy()keep="first"表示保留第一条,keep="last"表示保留最后一条。这里需要说明的是,按id去重会丢失部分信息,所以执行前一定要先确认id是应该唯一的字段,备份原始数据,并记录去重数量以便后续核对。
4.2 处理缺失值
处理缺失值没有“万能药”,关键是理解业务含义。
price缺失:严重影响价格分析或建模,建议直接删除对应行,或者用同区域、同房型的中位数填充。host_name缺失:对分析影响较小,可以用"Unknown"填充。last_review缺失:如果对应number_of_reviews为 0,缺失是正常的,表示该房源没有评论,可以保留缺失或填充NaT;如果评论数大于 0 但日期缺失,则建议删除该行或结合其他字段补全。name缺失:通常很少,但缺失时会导致文本分析失败,可以删除或填充"No title"。
一个比较稳妥的清洗逻辑:
# 删除关键字段缺失的行 df = df.dropna(subset=["id", "price", "room_type"]).copy() # 分类字段填充 df["host_name"] = df["host_name"].fillna("Unknown") # 日期字段保持缺失,后续用 pd.to_datetime 统一处理 df["last_review"] = pd.to_datetime(df["last_review"], errors="coerce")以上代码中,dropna(subset=[...])是“有取有舍”,只删除对业务影响最大的脏数据。
4.3 类型转换与价格标准化
Airbnb 数据里最常见的格式问题就是price。原始数据中它可能是"$120.00"、"$95"、"$1,200.00"甚至"$0.00",如果不处理就无法做数值统计。先看一下所有价格样本:
print(df["price"].unique()[:20])如果确认是字符串,清洗思路是去掉$和逗号,再转成数值:
def parse_price(value): if pd.isna(value): return pd.NA s = str(value).replace("$", "").replace(",", "").strip() return pd.to_numeric(s, errors="coerce") df["price"] = df["price"].apply(parse_price)执行后再次检查:
print(df["price"].dtype) print(df["price"].describe())如果price已经是float64,说明转换成功。此时可以观察最大最小值,判断是否存在明显异常值。
4.4 异常值处理
价格字段最常见的异常值是小于等于 0 的价格和极端高价格。小于等于 0 的价格通常是数据录入错误或测试房源,可以直接删除。极端高价需要结合业务判断,比如有些豪宅每晚几万美元也是合理数据。
一个实用的做法是:先看分位数,再结合业务阈值处理。
# 删除非正价格 df = df[df["price"] > 0].copy() # 查看 99% 分位数 print(df["price"].quantile(0.99))如果 99% 分位数是 1000 美元,那超过 5000 美元的数据大概率是有问题的,可以进一步排查。但我不建议直接粗暴剔除,因为 Airbnb 里确实存在高端房源。更推荐的做法是把可疑记录单独保存到output/suspicious_prices.csv,人工确认后再决定是否保留。
异常值处理之后,可以用describe()再次确认分布:
df["price"].describe()5. 数据整形
清洗完“脏”的部分,接下来把数据结构整理成更便于分析的样子。如果说清洗是“去病”,整形就是“塑形”。
5.1 列名规范化
Airbnb 原始 CSV 的列名有时包含空格或大小写混杂,建议统一成小写蛇形命名,便于后续写代码时不用频繁查列名。
df.columns = [col.strip().lower().replace(" ", "_") for col in df.columns]执行后,Number Of Reviews会被改成number_of_reviews。这里要注意,原始列名可能包含其他特殊字符,比如连字符或括号,建议按实际输出再调整一次替换规则。
5.2 文本字段清理
room_type这类分类字段常见问题是大小写和前后空格不一致。例如:
Entire home/aptPrivate roomprivate roomHotel room
如果不统一,value_counts()会把同一个类别拆成多个。清理方法:
df["room_type"] = df["room_type"].str.lower().str.strip()处理后再看类别分布:
print(df["room_type"].value_counts())可以看到所有类别被压缩成少数几个标准值。同样的方法也可以用于name、neighbourhood_group等文本列。
5.3 日期字段整形
日期字段的目标是统一成datetime类型,这样后续才能计算“距今天数”“按月份聚合”等特征。
date_columns = ["host_since", "last_review"] for col in date_columns: if col in df.columns: df[col] = pd.to_datetime(df[col], errors="coerce")errors="coerce"的作用是:无法解析的日期自动变成NaT,不会让程序中断。不过也要注意,pd.to_datetime在遇到不同日期格式混用时会弹出警告。如果 pandas 版本支持format="mixed",可以显式指定;如果版本较旧,建议先查看不合规样本再单独处理。
5.4 派生特征
整形阶段很适合做特征工程。基于已有字段,可以顺手生成几个对分析和建模都有用的新特征:
from datetime import datetime # 计算房源在平台上的天数 if "host_since" in df.columns: df["listing_age_days"] = (datetime.now() - df["host_since"]).dt.days # 是否拥有评论 df["has_reviews"] = df["number_of_reviews"].fillna(0).map(lambda x: 1 if x > 0 else 0) # 价格区间分箱,方便做分布分析 price_bins = [0, 100, 200, 500, 10000] price_labels = ["0-100", "100-200", "200-500", "500+"] df["price_range"] = pd.cut(df["price"], bins=price_bins, labels=price_labels, right=False)需要提醒的是,listing_age_days的计算结果会随运行时间变化。如果希望分析可复现,更推荐把“数据截止日期”作为参数传进来,比如固定使用asof_date = datetime(2024, 1, 1),而不是用datetime.now()。这一点在做时间敏感性分析时尤其重要。
5.5 区域坐标过滤与字段筛选
Airbnb 数据里,经纬度字段偶尔会有明显错误,比如经度或纬度越界。可以用一个简单的布尔条件过滤:
# 示例:仅保留特定城市的合理坐标范围,具体阈值需根据城市调整 valid_coords = ( df["latitude"].between(40.5, 41.0) & df["longitude"].between(-74.3, -73.7) ) df = df[valid_coords].copy()此外,整形阶段也是决定“最终保留哪些列”的时机。如果目标是价格预测,可能只需要room_type、price、availability_365、neighbourhood_group、派生特征等;如果目标是文本分析,那name就是核心列。用下面的方式保留指定字段:
keep_columns = [ "id", "name", "host_name", "neighbourhood_group", "room_type", "price", "price_range", "minimum_nights", "number_of_reviews", "has_reviews", "last_review", "availability_365", "listing_age_days" ] df_clean = df[keep_columns].copy()6. 完整实战案例
前面几节把流程拆成了一个个步骤,这一节用一个模拟数据集把整套流程串起来。示例数据是构造的,字段结构尽量对齐真实 Airbnb Listings,方便你替换成自己的 CSV。
6.1 构造一份模拟数据
import pandas as pd raw_rows = [ {"id": 1, "name": "Cozy Room in Brooklyn", "host_name": "Alice", "neighbourhood_group": "Brooklyn", "room_type": "Entire home/apt", "price": "$125.00", "minimum_nights": 3, "number_of_reviews": 45, "last_review": "2023-06-15", "availability_365": 280, "latitude": 40.678, "longitude": -73.944}, {"id": 2, "name": "Manhattan Studio", "host_name": "Bob", "neighbourhood_group": "Manhattan", "room_type": "Private room", "price": "$95", "minimum_nights": 1, "number_of_reviews": "5", "last_review": "2022/11/03", "availability_365": 0, "latitude": 40.753, "longitude": -73.983}, {"id": 3, "name": "Shared Room Near Park", "host_name": None, "neighbourhood_group": "Queens", "room_type": " shared room ", "price": "$1,200.00", "minimum_nights": 7, "number_of_reviews": 100, "last_review": "2023-02-10", "availability_365": 365, "latitude": 40.714, "longitude": -73.831}, {"id": 4, "name": "Luxury Apartment", "host_name": "Cathy", "neighbourhood_group": "Manhattan", "room_type": "Entire home/apt", "price": "$0.00", "minimum_nights": 2, "number_of_reviews": 12, "last_review": None, "availability_365": 60, "latitude": 40.760, "longitude": -73.984}, {"id": 5, "name": "Budget Room", "host_name": "David", "neighbourhood_group": "Bronx", "room_type": "Private Room", "price": "$99.00", "minimum_nights": 1, "number_of_reviews": 0, "last_review": None, "availability_365": 30, "latitude": 40.845, "longitude": -73.886}, {"id": 1, "name": "Cozy Room in Brooklyn", "host_name": "Alice", "neighbourhood_group": "Brooklyn", "room_type": "Entire home/apt", "price": "$125.00", "minimum_nights": 3, "number_of_reviews": 45, "last_review": "2023-06-15", "availability_365": 280, "latitude": 40.678, "longitude": -73.944}, {"id": 6, "name": "Invalid Coord House", "host_name": "Eve", "neighbourhood_group": "Manhattan", "room_type": "Hotel room", "price": "$300.00", "minimum_nights": 2, "number_of_reviews": 20, "last_review": "2023-01-01", "availability_365": 10, "latitude": 100.00, "longitude": -73.984}, ] df_raw = pd.DataFrame(raw_rows) df_raw.to_csv("listings_demo.csv", index=False) print(df_raw)上面的模拟数据涵盖了重复行、缺失值、价格格式混乱、日期格式混杂、经纬度异常等常见脏数据问题。
6.2 编写完整清洗整形函数
下面把前面的步骤封装成一个函数,后续处理任意 CSV 时只需把路径传入:
from datetime import datetime CITY_LAT_RANGE = (40.5, 41.0) CITY_LON_RANGE = (-74.3, -73.7) def parse_price(value): if pd.isna(value): return pd.NA s = str(value).replace("$", "").replace(",", "").strip() return pd.to_numeric(s, errors="coerce") def clean_and_shape_listings(df, asof_date=None): # 避免修改原始数据 data = df.copy() # 1. 列名规范化 data.columns = [col.strip().lower().replace(" ", "_") for col in data.columns] # 2. 按 id 去重 before = len(data) data = data.drop_duplicates(subset=["id"], keep="first") print(f"去重前行数: {before}, 去重后行数: {len(data)}") # 3. 英文列名中如果存在,删除关键字段缺失的行 data = data.dropna(subset=["id", "price", "room_type"]).copy() # 4. 价格解析 data["price"] = data["price"].apply(parse_price) # 5. 删除非正价格 data = data[data["price"] > 0].copy() # 6. 文本字段规范化 data["room_type"] = data["room_type"].astype(str).str.lower().str.strip() data["name"] = data["name"].fillna("No title").astype(str).str.strip() data["host_name"] = data["host_name"].fillna("Unknown").astype(str).str.strip() # 7. 日期全量化 for col in ["host_since", "last_review"]: if col in data.columns: data[col] = pd.to_datetime(data[col], errors="coerce") # 8. 数值字段统一 for col in ["minimum_nights", "number_of_reviews", "availability_365"]: if col in data.columns: data[col] = pd.to_numeric(data[col], errors="coerce").fillna(0) # 9. 删除明显越界的坐标 data = data[data["latitude"].between(*CITY_LAT_RANGE)] data = data[data["longitude"].between(*CITY_LON_RANGE)] # 10. 派生特征 if asof_date is None: asof_date = datetime.now() data["listing_age_days"] = (pd.Timestamp(asof_date) - data["host_since"]).dt.days data["has_reviews"] = (data["number_of_reviews"] > 0).astype(int) data["price_range"] = pd.cut( data["price"], bins=[0, 100, 200, 500, 100000], labels=["0-100", "100-200", "200-500", "500+"], right=False ) # 11. 保留目标字段 keep_columns = [ "id", "name", "host_name", "neighbourhood_group", "room_type", "price", "price_range", "minimum_nights", "number_of_reviews", "has_reviews", "last_review", "availability_365", "listing_age_days" ] keep_columns = [col for col in keep_columns if col in data.columns] data = data[keep_columns].copy() return data使用方式:
df_clean = clean_and_shape_listings(df_raw, asof_date=datetime(2024, 1, 1)) print(df_clean)6.3 运行与验证
运行代码后,预期输出大致是:
去重前行数: 7, 去重后行数: 6 清洗后行数: 4最终清洗后的数据集应该只剩 4 行,因为重复行、缺失价格的行、非正价格、错误坐标分别被删除。price变成float64,last_review变成datetime64[ns],room_type变成统一的小写格式。
6.4 导出结果
清洗完成后导出到output目录:
import os os.makedirs("output", exist_ok=True) df_clean.to_csv("output/listings_clean.csv", index=False)也可以顺手保存一个带data_version的日期后缀文件,方便区分版本:
df_clean.to_csv("output/listings_clean_20240101.csv", index=False)这里使用os.makedirs("output", exist_ok=True)能保证目录不存在时自动创建。后面常见问题里会提到,不做这一步就可能碰到目录创建失败。
7. 常见问题与排查思路
处理 Airbnb Listings 数据时,下面几个问题出现频率很高:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
price无法转成数字 | 字符串中存在$、逗号、空格或其他符号 | 先str.replace()清理,再用pd.to_numeric(errors="coerce") |
日期解析报错或出现大量NaT | 日期格式混杂,如2023-06-15和2022/11/03共存 | 用pd.to_datetime(..., errors="coerce"),然后单独排查转为NaT的样本 |
| 去重后数据量骤减 | 误把不该当作主键的列放入subset | 先确认业务主键,比如id是否确实唯一 |
room_type类别非常多 | 大小写、空格、拼写不统一 | 统一转小写、去空格,再用value_counts()检查 |
| 导出时目录不存在 | 使用了相对路径且目标目录未创建 | 使用os.makedirs("output", exist_ok=True)或Path.mkdir(parents=True, exist_ok=True) |
下面单独展开两个比较典型的问题。
7.1 价格字段解析失败
如果parse_price之后出现大量NaN,先检查原始字符串到底长什么样:
bad_price = df[df["price"].isna()] print(bad_price["price"].head(20))常见情况是价格字段里混入了不规则字符,例如"价格面议"、"$1,200.00/月"等。对于这类数据,建议保留原始列,并把解析结果单独存为新列,避免把原始信息丢失。
7.2 重复值删除过度
drop_duplicates(subset=["id"])如果删掉的行数量异常,可能是同一个id下实际有不同的房源信息,例如共享房源中不同房型共用同一id。遇到这种情况,可以改为按id + room_type + price去重,或者在前一步先通过sort_values()把质量更高的行排到前面,再去重。
7.3 创建输出目录时报 cannot mkdir structure meed cleaning
如果你在自动化处理脚本里遇到类似cannot mkdir structure meed cleaning的提示,不要只盯着目录名本身。这个报错的意思是:脚本尝试创建目录,但目录结构没有准备好,需要先清理或调整。常见的触发原因有三个。
第一,目标路径上已经存在一个同名普通文件。例如你有一个文件叫output,脚本又想创建同名的output/目录,系统就会拒绝。解决方法是先检查并重命名或删除冲突文件。
import os from pathlib import Path output_dir = Path("output") if output_dir.exists() and not output_dir.is_dir(): output_dir.rename("output_conflict_backup") output_dir.mkdir(parents=True, exist_ok=True)第二,父目录不存在,且脚本没有递归创建目录。在 Python 中,只用os.mkdir("data/processed")时,如果data或processed都不存在,会直接报错。推荐使用:
from pathlib import Path Path("output/listings_clean").mkdir(parents=True, exist_ok=True)第三,目录里残留旧的中间文件导致脚本状态混乱。这里的structure meed cleaning其实是在提醒你:当前目录结构已经和脚本预期不一致,建议把旧的临时目录清理掉,重新初始化一个干净的输出目录。
在数据工程脚本中,建议在脚本开头固定一段“初始化目录”的逻辑,把错误消灭在第一步:
import shutil from pathlib import Path output_dir = Path("output") if output_dir.exists(): shutil.rmtree(output_dir) output_dir.mkdir(parents=True, exist_ok=True)这个方案适合完全由脚本生成的临时目录,但如果目录里有重要手工文件,删除前一定要先备份或加确认条件。
8. 最佳实践与工程建议
如果只是做一次性的探索性分析,前面几节已经够用。但如果这个清洗流程要反复使用,或者要交给团队其他成员使用,下面这些工程习惯值得注意。
8.1 保留原始数据副本
清洗前先备份原始文件。最简单的方法是把原始 CSV 复制为带raw后缀的文件,或者把原始DataFrame保留在内存中不覆盖。数据清洗是不可逆操作,一旦在原文件上直接修改,后面想恢复就非常麻烦。
df_raw_backup = df_raw.copy()8.2 把清洗流程封装成函数
不要把所有步骤一股脑写在 Jupyter Notebook 的某个单元格里,建议封装成clean_and_shape_listings()这样的函数。这样做的优势在于:可以批量处理多个城市的 Airbnb 数据;可以接入测试框架;可以用相同的清洗规则处理训练集和预测集,避免数据泄漏或规则不一致。
8.3 记录清洗日志
在函数里用print()或日志库记录每一步删除的行数和关键统计量。比如:
print(f"删除缺失价格行: {len(df) - len(df.dropna(subset=['price']))}")清洗结束后,可以自动生成一份包含原始数据行数、清洗后行数、删除原因统计的日志文件,便于复盘。
8.4 控制输出字段范围
如果清洗后的数据要共享给同事或用于外部展示,建议移除包含个人隐私的字段。Airbnb Listings 原始数据中通常包含房东姓名、精确经纬度等敏感信息,不必要的字段不要导出,分析时只保留分析需要的列即可,这也是数据处理中的最小权限原则。
8.5 使用配置文件管理路径
如果脚本要在不同机器上运行,不要把文件路径硬编码在每个脚本里。可以写一个简单的配置文件config.yaml或config.json,统一管理数据路径、城市经纬度范围、价格阈值等参数。这样换城市或换服务器时,只需要修改配置,不用动代码。
{ "raw_data_path": "data/listings.csv", "output_dir": "output", "city_lat_range": [40.5, 41.0], "city_lon_range": [-74.3, -73.7] }8.6 大数据量下的读取策略
如果listings.csv有几个 GB,一次读入内存会比较吃力。此时可以用chunksize分块读取,先清洗每一块,再把清洗后的结果累计到最终表中:
chunks = pd.read_csv("data/listings.csv", chunksize=100000) df_clean_list = [] for chunk in chunks: df_clean_list.append(clean_and_shape_listings(chunk)) df_all = pd.concat(df_clean_list, ignore_index=True)不过分块处理时要注意,重复值去重和全局统计量(比如分位数、缺失率)不能简单地按块计算,需要先在全局层面确认规则,然后再分块清洗。
8.7 用 pandas 内置方法减少代码量
很多清洗操作其实有更简洁的写法。例如判空可以用df["price"].str.contains("\\$"),数值规范化可以用df["price"].str.replace("[$,]", "", regex=True)。但代码简洁的前提是可读性,建议在团队协作时优先选择注释完整、逻辑清晰的写法。
结语
Airbnb Listings 数据的清洗和整形并不难,难的是把每一步想清楚:哪个字段是业务主键,缺失值应该删除还是填充,异常价格是该剔除还是单独保留。建议你拿到一份新数据后,不要急着写模型,先花一小时把本文的流程完整跑一遍,做出第一版干净数据表。后续做可视化、特征工程和模型训练时,你会明显感觉到数据顺手很多。
最后说一个很实用的习惯:每次跑清洗脚本前,先复制一份原始 CSV,命名为listings_raw_backup.csv。数据清洗是不可逆操作,很多翻车案例都是因为在原文件上直接修改后再也恢复不了。把这条记在项目的 README 里,能省掉不少麻烦。
