Airbnb数据清洗与整形:pandas实战处理脏数据全流程
2026/8/26 3:04:44 网站建设 项目流程

做数据分析的人经常遇到一个场景:从网上下载了一份看起来很规整的数据集,比如 Airbnb Listings,打开一看,密密麻麻的字段,有价格、有评论、有坐标、有房东信息。你以为接下来可以直接建模了,结果df.info()一跑,发现价格是字符串、缺失值占了三成、同一套房源重复出现了五次,甚至还有经纬度在海洋里的数据。

这时候才意识到,真正决定分析项目成败的,往往不是后面用了什么高级模型,而是前面这两个环节:数据清洗(Data Cleaning)数据整形(Data Shaping)

这篇文章就以 Airbnb Listings 数据集为例,完整拆解一份原始房源数据从“没法用”到“能用、好用”的全过程。我们会先讲清楚清洗和整形各自解决什么问题,再给出可复制的 Python 代码,最后补充实际项目中的排查思路和工程建议。读完这篇文章,你不仅会处理 Airbnb 数据,还能把同样的方法迁移到电商商品、招聘信息、二手房挂牌等其他表格型数据集上。

1. 这篇文章真正要解决的问题

很多初学者对数据清洗有个误会,觉得它就是“删掉空值”“去一下重复”,属于体力活,没什么技术含量。但真实项目里,数据清洗和整形往往是耗时最久、最容易出错、也最影响最终结论质量的环节。曾有统计说数据分析师 60% 以上的时间花在数据准备上,这个比例放在 Airbnb 这类真实数据集上一点都不夸张。

Airbnb Listings 数据集之所以适合拿来学习,是因为它具备真实业务数据的几乎所有典型问题:

  • 字段类型错乱:价格列里包含了$,,甚至可能是"$1,200.00"这种格式,pandas 默认读进来是字符串。
  • 缺失值复杂:有些字段缺失是可以直接删的,有些字段缺失需要填充,还有些字段缺失本身就是一种业务信号。
  • 重复数据:同一房源可能因为房东多次更新、爬虫重复抓取而出现多行记录。
  • 异常值:价格可能填成了 0,评论数可能是天文数字,经纬度可能完全偏离城市范围。
  • 存储格式不适合分析:日期是字符串、卧室数写在文本里、多个设施用逗号拼在一个单元格里,比如"WiFi,Kitchen,Heating"

如果跳过这些问题直接建模,轻则指标失真,重则整个分析结论被带偏。比如价格列不转成数值,df['price'].mean()就会直接报错;如果错误地忽略了重复项,统计房源数量时会虚高;如果不对异常价格做处理,中位数可能被几个极端值拉到一个完全不符合市场认知的位置。

这篇文章要解决的问题,就是建立一套可复用的 Airbnb Listings 数据清洗与数据整形流程。我们会从最原始的 CSV 文件开始,一步一步完成数据加载、字段检查、缺失值处理、重复值处理、异常值处理、类型转换、特征提取和宽表转长表。每一步都会解释“为什么这么做”,而不是只给代码。

2. 数据清洗与数据整形:到底差在哪

先做一个概念区分。很多文章把 Data Cleaning 和 Data Shaping 混在一起讲,但在工程实践中,它们的职责边界很清晰。

数据清洗(Data Cleaning)解决的是“数据对不对”的问题。它处理的是脏数据,包括缺失值、重复值、异常值、格式错误、类型错误、不一致的取值等。清洗的目标是让每一行数据真实、准确、可比较。比如把"$100.00"变成100.0,把"3 beds"变成3,把明显偏离城市范围的经纬度删除,这些都是清洗。

数据整形(Data Shaping)解决的是“数据好不好用”的问题。它处理的是数据的组织方式,包括列的选择与重命名、行与列的转换、从现有字段中提取新特征、把宽表变长表、把长表变宽表等。整形不改变数据本身的真实性,但会改变数据的结构,让它更适合后续可视化、建模或统计分析。比如从last_review日期中提取出年份和月份,把amenities从逗号分隔的字符串拆成多个布尔列,把每个房源的多个设施记录从一行展开成多行,这些都是整形。

可以用一句话概括:清洗是把数据变干净,整形是把数据变顺手。

实际项目中,这两个步骤往往是交替进行的。比如你发现price列有缺失值,这是清洗问题;但如果你想把价格按照neighbourhood_group分组填充中位数,这又涉及分组和转换,属于整形的思路。所以本文不会强行把它们割裂成两条互不相干的流程,而是按照“先清洗后整形”的顺序组织,这样对新手最友好。

下面用一个表格对比它们的核心差异:

对比维度数据清洗(Data Cleaning)数据整形(Data Shaping)
核心问题数据对不对数据好不好用
主要操作处理缺失值、重复值、异常值、类型转换列选、重命名、行列转换、特征提取
典型字段price、last_review、coordinatesamenities、host_verifications、date
失败后果分析结果失真分析无法开展或效率低
判断标准每一行数据真实可靠数据结构适合当前分析任务

3. 环境准备与数据集说明

整个流程只需要 Python 环境和 pandas 库,不需要额外安装机器学习框架。

建议在虚拟环境中操作,避免依赖冲突。以下是我推荐的安装方式:

python -m venv venv source venv/bin/activate # Windows 下使用 venv\Scripts\activate pip install pandas numpy jupyter

版本方面,pandas 的 1.x 和 2.x 都支持本文代码,具体以你本机安装的版本为准。如果是在 Jupyter Notebook 里操作,记得把代码按单元格拆开执行,方便随时查看中间结果。

数据集方面,本文以 Airbnb 公开的 Listings 数据集为例。这类数据一般以 CSV 格式提供,包含房源 ID、房东 ID、价格、房间类型、经纬度、评论数、最低住宿晚数、发布日期等几十个字段。你可以在 Airbnb 官方的公开数据页面获取,也可以使用 Kaggle 和 Inside Airbnb 上的版本。不同渠道的字段略有差异,但核心字段基本一致,本文代码会在读取数据后先做字段概览,所以即使字段名不完全相同也能快速调整。

需要特别说明的是,本文不会依赖某个特定版本的 CSV 文件。真实项目中,你拿到的数据字段可能比我这里演示的多或少,但只要掌握了“先概览、再清洗、后整形”的思路,任何版本的 Airbnb Listings 数据都能处理。

4. Airbnb Listings 数据清洗核心流程拆解

从这一节开始进入正题。我们先读取数据并做初步体检,然后按照“缺失值 → 重复值 → 异常值 → 类型转换”的顺序完成清洗。

4.1 读取数据并做初步体检

拿到 CSV 后的第一件事不是急着清洗,而是先搞清楚数据长什么样。建议按下面的代码先做一轮快速体检:

import pandas as pd import numpy as np # 读取数据 df = pd.read_csv('listings.csv') # 先看行列数 print("数据集形状:", df.shape) # 看前几行,了解字段类型和大概内容 print(df.head(3).T) # 看字段类型 print(df.dtypes) # 看缺失情况 print(df.isnull().sum().sort_values(ascending=False).head(20))

这里head(3).T是为了把一行数据转置成竖排,方便观察每个字段的值。dtypes能告诉我们哪些字段被读成了对象类型,哪些是数值类型。isnull().sum()则是看缺失值分布。

这一步跑完,你通常会看到两种情况:

  • 很多字段是object类型,比如pricelast_reviewamenities
  • 缺失值集中在少数几个字段,比如last_reviewreview_scores_ratingneighbourhood_group

在这个阶段不需要做任何处理,先记录观察结果,再进入下一步。

4.2 缺失值处理:先分类,再决定策略

缺失值处理的错误做法是直接dropna()一把梭。很多新手对所有缺失字段统一删除,结果把数据集删得只剩一半,或者把有业务含义的缺失变成无意义的数据损失。

正确的做法是先回答三个问题:

  1. 这个字段的缺失占比是多少?
  2. 这个字段后续分析中是否需要用到?
  3. 缺失本身有没有业务含义?

比如在 Airbnb 数据中:

  • idhost_idprice这些关键字段如果缺失,通常建议删除对应行,因为无法填充且影响核心分析。
  • neighbourhood_group缺失但latitudelongitude存在时,可以考虑通过逆地理编码补全,或者直接标记为“未知区域”。
  • last_review缺失往往意味着该房源从未收到过评论,这是一个有业务含义的信号,不应该简单删除,而应该在整形阶段用 0 或“无评论”标记。
  • review_scores_rating缺失,如果该房源评论数为 0,那缺失是合理的,不需要强行填一个分数。

下面是一个实用的处理策略示例:

# 先看关键字段缺失比例 key_cols = ['id', 'host_id', 'price', 'last_review', 'review_scores_rating'] missing_ratio = df[key_cols].isnull().mean().sort_values(ascending=False) print(missing_ratio) # 策略1:核心标识字段缺失,直接删除 df = df.dropna(subset=['id', 'host_id', 'price']) # 策略2:对评分类字段,先看评论数;评论数为0则缺失合理 # 将缺失评分填充为0,表示暂无评分 df['review_scores_rating'] = df['review_scores_rating'].fillna(0) # 策略3:last_review 缺失,填充为 NaT,后续整形时转为“无评论” df['last_review'] = pd.to_datetime(df['last_review'], errors='coerce')

这里的关键点是:先给缺失值定性,再选择删除、填充还是保留。如果缺失字段用于建模且缺失比例超过 50%,基本可以放弃该字段;如果缺失集中在少数行,直接删除这些行更省事;如果缺失字段具有业务含义,就不要强行填充。

4.3 重复值处理:不能只按整行去重

数据去重也有坑。df.drop_duplicates()默认按整行完全重复来判断,但实际业务场景中,Airbnb 房源很可能只是部分字段重复。

举例来说,同一个房源被爬虫抓取了两次,可能idhost_idprice完全相同,但last_review因为抓取时间不同而不同。这时如果按整行去重,根本去不掉。正确做法是选定一组关键字段作为去重依据。

# 方案1:按整行去重 df = df.drop_duplicates() # 方案2:按房源ID去重,保留最后一次抓取记录 df = df.sort_values('last_scraped', ascending=False).drop_duplicates(subset=['id'], keep='first')

如果数据里没有last_scraped字段,就按id去重并保留第一条即可:

df = df.drop_duplicates(subset=['id'], keep='first')

去重之后,记得重置索引:

df = df.reset_index(drop=True)

这里需要说明,drop_duplicatessubset参数很灵活。比如你想知道同一个房东是不是重复发布了同样的房源,可以把host_idname组合起来判断。这个决策完全取决于业务上如何定义“重复”。

4.4 异常值处理:用业务常识圈定合理范围

异常值的判定不能只靠统计方法,还必须结合业务常识。Airbnb 数据里有几个典型场景:

价格字段中,price为 0 是明显异常的。即使是免费房源,也会用特殊字段标识,不会直接把价格写成 0。price高达几万美元而房间类型只是“Private room”,也很可疑。这类极端值会严重影响后续的均值计算。

处理思路是先看分布:

print(df['price'].describe())

然后结合业务常识设定合理范围。比如可以认为,正常房源的单晚价格应该在 10 到 2000 美元之间,超出这个区间单独检查,而不是直接一刀切。

# 先转成数值 df['price'] = df['price'].replace('[\$,]', '', regex=True).astype(float) # 查看异常分布 print(df['price'].describe(percentiles=[0.01, 0.05, 0.5, 0.95, 0.99])) # 用业务阈值过滤 df = df[(df['price'] >= 10) & (df['price'] <= 2000)]

经纬度也可以用业务常识判断。比如目标城市是纽约,那么纬度应该在 40.5 到 41.0 之间,经度应该在 -74.3 到 -73.5 之间。超出这个范围的数据,要么是抓取错误,要么是坐标漂移。

df = df[(df['latitude'].between(40.5, 41.0)) & (df['longitude'].between(-74.3, -73.5))]

这种基于业务范围过滤的方式,比单纯靠 3σ 原则更加稳健,因为你是在用领域知识约束数据,而不是让数据自己定义“正常”。

4.5 类型转换:用pd.to_numericpd.to_datetime收尾

清洗的最后一步是类型转换。pandas 在读 CSV 时,经常把数值列读成字符串,尤其是包含货币符号、千分位分隔符时更是如此。

# 价格转数值(前面已经做过) df['price'] = df['price'].replace('[\$,]', '', regex=True).astype(float) # 评论数转数值 df['number_of_reviews'] = pd.to_numeric(df['number_of_reviews'], errors='coerce') # 日期转 datetime df['last_review'] = pd.to_datetime(df['last_review'], errors='coerce') # 最低住宿晚数转整数 df['minimum_nights'] = pd.to_numeric(df['minimum_nights'], errors='coerce').astype('Int64')

这里要注意errors='coerce'的作用。它会把无法转换的值变成NaN,而不是直接报错中断。这在真实数据集中非常重要,因为你永远不知道 CSV 里会混进什么奇怪内容。转换之后,再次用df.dtypes检查,确保关键字段都变成了float64datetime64[ns]等对应类型。

5. Airbnb Listings 数据整形与特征工程实战

清洗完成之后,数据已经“干净”了,但还不一定“好用”。这一节我们专注于数据整形,目标是把原始字段转换成更利于分析的结构。

5.1 列选择与重命名

Airbnb Listings 原始数据可能有 70 多个字段,但实际分析往往只需要其中十几个。建议先做一次列选择,避免后面代码频繁在字段名上打转。

# 只保留分析需要的字段 cols = [ 'id', 'host_id', 'host_name', 'neighbourhood_group', 'neighbourhood', 'latitude', 'longitude', 'room_type', 'price', 'minimum_nights', 'number_of_reviews', 'last_review', 'reviews_per_month', 'calculated_host_listings_count', 'availability_365' ] df = df[cols] # 重命名为更简洁、更符合 Python 习惯的列名 df = df.rename(columns={ 'neighbourhood_group': 'district', 'calculated_host_listings_count': 'host_listings_count', 'availability_365': 'available_days', 'number_of_reviews': 'review_count' })

重命名时推荐使用小写+下划线的命名规范,这样可以减少后续写代码时的大写切换。列名本身就是代码的一部分,一个好的列名能节省大量沟通成本。

5.2 从日期字段中提取新特征

last_review是日期类型后,可以方便地提取年份和月份。比如你想研究“哪个季节房源评论最多”,就需要把月份单独提取出来。

df['last_review_year'] = df['last_review'].dt.year df['last_review_month'] = df['last_review'].dt.month # 缺失日期的房源,没有评论,用 0 填充年份 df['last_review_year'] = df['last_review_year'].fillna(0).astype(int) df['last_review_month'] = df['last_review_month'].fillna(0).astype(int)

这里再次体现了清洗和整形的衔接。last_review的缺失值在清洗阶段被保留为NaT,到整形阶段才转化为“0年0月”这种业务语义。整个过程分成两步走,每一步都清楚可控。

5.3 对连续型字段做分段

price是连续数值,但有些分析场景下,把它分成几个价格带更有价值。比如看“不同价位房源的评论数差异”,用数值价格当然能做散点图,但用价格带做分组对比更直观。

bins = [0, 50, 100, 200, 500, 5000] labels = ['0-50', '50-100', '100-200', '200-500', '500+'] df['price_range'] = pd.cut(df['price'], bins=bins, labels=labels)

pd.cut是等宽分段,如果数据分布极不均匀,也可以用pd.qcut做等频分段,让每个区间样本量接近。选哪个取决于分析目的,没有绝对标准。

5.4 从文本字段中提取标志位

Airbnb 数据中,room_type的取值有Entire home/aptPrivate roomShared roomHotel room。如果后续建模时想把房间类型作为特征,最简单的办法是 One-Hot 编码。

# 房间类型独热编码 room_type_dummies = pd.get_dummies(df['room_type'], prefix='room') df = pd.concat([df, room_type_dummies], axis=1)

需要注意,pd.get_dummies默认生成的列名会包含原始值中的空格和斜杠,比如room_Entire home/apt。这种列名在后续用字符串操作时容易出问题,建议在生成后统一重命名:

room_type_dummies.columns = [ col.lower().replace(' ', '_').replace('/', '_') for col in room_type_dummies.columns ]

处理完后,room_type原始列可以保留,也可以删除。建议在建模前删除,因为独热编码已经表达了同样的信息。

5.5 逗号拼接字段的拆解

Airbnb 的amenities字段是典型的“一列多值”结构,比如:

"WiFi,Kitchen,Heating,Smoke detector,Essentials"

这样的数据直接用于分析很困难。你很难用df['amenities'].value_counts()知道有多少房源提供 WiFi,因为每个单元格都包含多个设施。正确的做法是把多个值拆开。

如果你只需要判断“是否包含某个设施”,可以先生成标志位:

def has_amenity(amenities_str, keyword): return int(keyword in str(amenities_str)) df['has_wifi'] = df['amenities'].apply(lambda x: has_amenity(x, 'WiFi')) df['has_kitchen'] = df['amenities'].apply(lambda x: has_amenity(x, 'Kitchen')) df['has_heating'] = df['amenities'].apply(lambda x: has_amenity(x, 'Heating'))

如果你想把设施从宽表转成长表,为每个房源和每个设施生成一行记录,可以使用explode

# 先把字符串拆成列表 df['amenities_list'] = df['amenities'].str.split(',') # 拆分为长表 amenity_long = df.explode('amenities_list') print(amenity_long[['id', 'amenities_list']].head(10))

explode是 pandas 整形中非常强大的方法。它的作用是把一个单元格中的列表展开成多行,同时复制其他字段。这在处理标签、分类、多值属性时非常常用。

5.6 长表和宽表的相互转换

除了explodepivotmelt也是数据整形的高频操作。这里用一个简单例子说明它们的应用场景。

假设你想按districtroom_type统计平均价格,初始数据是每个房源一行。用pivot_table可以把这个长表转换成以district为行、room_type为列的宽表:

pivot_price = df.pivot_table( index='district', columns='room_type', values='price', aggfunc='mean' ) print(pivot_price)

反过来,如果你从某个接口拿到的是宽表数据,每一行是一个district,每一列是一种room_type,但你想用 matplotlib 或 seaborn 画分组柱状图,就需要转回长表。这时候用melt

long_price = pivot_price.reset_index().melt( id_vars='district', var_name='room_type', value_name='avg_price' ) print(long_price.head())

长表和宽表的转换是数据整形里最考验理解力的部分。记住一个核心规则:行是样本、列是特征,就是宽表;用多行表达一个样本的多个属性,就是长表。分析建模常用宽表,可视化绘图和部分统计模型常用长表。

6. 运行结果与效果验证

清洗和整形的代码写完,不是跑完没报错就算成功。你需要一套验证方法来确认每一步都做对了。

6.1 验证清洗效果

清洗完成后的验证清单:

# 1. 没有缺失值(或缺失值已按业务规则处理) print("清洗后缺失值总数:", df.isnull().sum().sum()) # 2. 没有重复房源 print("房源 ID 重复数:", df['id'].duplicated().sum()) # 3. 关键字段类型正确 print(df[['price', 'review_count', 'last_review']].dtypes) # 4. 价格字段的统计量符合业务预期 print(df['price'].describe()) # 5. 数据范围没有异常 print("经纬度范围:", df['latitude'].min(), df['latitude'].max(), df['longitude'].min(), df['longitude'].max())

预期输出中,缺失值总数应该为 0 或者严格等于你刻意保留的、具有业务含义的缺失值。id重复数应该是 0。price应该是float64review_count应该是数值类型,last_review应该是datetime64[ns]。价格的最大值不超过你设定的业务阈值。

6.2 验证整形效果

整形后的验证重点在于结构是否符合后续分析需要。

# 1. 新特征是否生成 print(df.columns.tolist()) # 2. 价格分段是否均匀分布 print(df['price_range'].value_counts()) # 3. 独热编码列是否只有 0/1 print(df[['room_entire_home_apt', 'room_private_room', 'room_shared_room']].apply(lambda x: x.unique())) # 4. 长表展开后,每个房源的设施行数是否准确 amenity_long_counts = amenity_long.groupby('id').size() print("每个房源平均设施数:", amenity_long_counts.mean())

如果某个price_range区间内房源数极少,比如 0 个,说明你的分箱边界可能不合理,需要调整 bins。如果独热编码列出现了大于 1 的值,说明room_type列存在异常值,需要回到清洗阶段处理。

6.3 用可视化快速验证

除了打印统计量,画图是发现数据问题的另一个高效手段。

import matplotlib.pyplot as plt # 检查价格分布 df['price'].hist(bins=50) plt.title('Price Distribution After Cleaning') plt.xlabel('Price') plt.ylabel('Count') plt.show()

如果价格分布仍然出现极端长尾,比如右侧拖出很长一条尾巴,并且明显不符合业务认知,就需要回到异常值过滤步骤调整阈值。如果分布相对集中,整体呈右偏形态,这是长尾数据的正常表现,可以接受。

7. 常见问题与排查思路

数据清洗和整形过程中,有几个问题几乎每个人都会遇到。这里整理成表格,方便你直接对照排查。

问题现象可能原因排查方式解决方案
pd.read_csv后价格列是 objectCSV 中包含$,等符号查看df['price'].head(10)确认格式replace去掉符号后astype(float)
dropna()后数据量骤降缺失值占比过高,不该统一删除先计算各字段缺失比例分字段制定策略,不要dropna()一把梭
df['price'].mean()报错列类型仍是字符串查看df.dtypes先做类型转换再计算
日期字段转 datetime 失败包含NaT或非法格式串errors='coerce'后检查新产生的缺失值修正源格式或统一填充规则
去重后 ID 仍有重复重复行的id相同但其他字段不同检查df['id'].duplicated().sum()按 ID 去重时指定subset=['id']
独热编码生成的列名带特殊字符原始分类取值包含空格和斜杠打印df['room_type'].unique()独热编码后统一重命名列名
价格异常值导致均值远高于中位数存在极端高价或 0 价describe(percentiles=[...])看分布按业务阈值过滤或做分箱
explode后数据量暴增设施字段包含大量值检查展开前后行数差异确认展开口径是否符合业务需求

这里特别想强调一个新手容易犯的错误:看到read_csv不报错,就以为数据至少能用了。实际上,pandas 在数据类型解析失败时默认不会报错,而是把整列读成object类型。所以不报错不等于数据类型正确,必须在清洗后主动检查dtypes

8. 最佳实践与工程建议

数据清洗和整形做多了以后,会发现有章可循。下面这些建议来自实际项目中的经验总结,能帮你少走很多弯路。

8.1 清洗流程要写成函数,而不是脚本

很多人习惯在 Notebook 里从头到尾写一长串清洗代码,跑完就完事。但真实项目的麻烦在于,数据源会更新,比如 Airbnb 每月发布一次新数据。如果清洗逻辑是散落的一堆单元格,每次更新都要人工重跑,很容易漏步骤。

建议把清洗和整形封装成函数:

def clean_listings(df): """Airbnb Listings 清洗函数""" df = df.copy() df = df.drop_duplicates(subset=['id'], keep='first') df = df.dropna(subset=['id', 'host_id', 'price']) df['price'] = df['price'].replace('[\$,]', '', regex=True).astype(float) df['last_review'] = pd.to_datetime(df['last_review'], errors='coerce') df = df[(df['price'] >= 10) & (df['price'] <= 2000)] return df.reset_index(drop=True) def shape_listings(df): """Airbnb Listings 整形函数""" df = df.copy() df['last_review_year'] = df['last_review'].dt.year df['last_review_month'] = df['last_review'].dt.month df['price_range'] = pd.cut(df['price'], bins=[0, 50, 100, 200, 500, 5000]) return df

封装成函数之后,每次数据更新只需要重新执行clean_listings(new_df),而且函数名本身就在记录流程,代码的可读性和可维护性能提升一个档次。

8.2 保留原始数据,清洗结果另存

一个非常实用的习惯:永远保留一份原始数据,清洗后的数据另存为新的变量或者新的文件。

# 先备份 raw_df = pd.read_csv('listings.csv') df = raw_df.copy() # 在 df 上执行清洗和整形 ...

这样做的好处是,如果清洗规则需要调整,比如修改价格阈值,你不需要重新下载数据,只需要重新执行从raw_df.copy()开始的单元格。另一个好处是,当你需要追溯某条数据为什么被删除时,可以回到原始数据检查。

8.3 每一步清洗都要可审计

在团队协作或生产环境中,数据清洗规则必须有记录。最简单的做法是在 Notebook 中为每一个处理步骤写好注释,并且说明依据。

# 删除价格小于10美元或大于2000美元的房源 # 依据:业务上低于10美元视为测试房源,高于2000美元视为极端高价 # 影响:影响约 1.2% 的行,需业务确认后执行 df = df[(df['price'] >= 10) & (df['price'] <= 2000)]

如果团队使用版本管理工具,清洗脚本也应该纳入管理。数据清洗不是一次性工作,它跟业务代码一样需要 review,需要回溯。

8.4 区分处理与分析,避免过度清洗

数据清洗有一个容易走偏的方向:为了追求“完美数据”而过度清洗。比如某个字段缺失率达到 30%,有人会想尽办法用机器学习模型去填充它。但在大多数分析场景下,删除该字段或者简单地标记缺失,比复杂填充更稳妥。

原则是:清洗的目的是让数据能回答问题,而不是让数据完美。如果某个字段在你要回答的问题中根本用不到,就不需要为它花费大量时间。先明确分析目标,再决定清洗的深度,效率会高很多。

8.5 在项目早期就约定列名规范

Airbnb 数据集来自公开渠道,列名比较混乱,比如number_of_reviewsreviews_per_month很相似,容易混淆。在项目开始时就统一重命名,可以避免后面写出df['number_of_reviews']df['reviews_per_month']混用的情况。

推荐的命名规范是:全部小写,单词间用下划线连接。例如review_countavailable_dayshost_listings_count。这样写代码时不需要记忆大小写,也符合 Python 社区的习惯。

8.6 注意数据来源与合规性

Airbnb 公开数据集的获取和使用,需要注意数据来源的授权条款。不同渠道发布的 Airbnb 数据集,使用限制不完全相同,有些仅限非商业用途。如果项目用于商业分析,建议先确认数据的使用许可。

另外,Airbnb 数据中包含经纬度等空间信息,在公开发表分析结果时,如果房源数量较少,应注意脱敏,避免从细粒度坐标反推出具体房源位置。这类数据合规问题在真实项目里越来越重要,建议从一开始就养成习惯。

9. 总结与后续学习方向

这篇文章以 Airbnb Listings 数据集为例,完整走了一遍数据清洗和整形流程。核心结论可以浓缩成三句话:

第一,数据清洗解决的是“数据对不对”的问题,核心操作是缺失值、重复值、异常值和类型转换;数据整形解决的是“数据好不好用”的问题,核心操作是列选择、特征提取、宽表长表转换。两者不能互相替代,也不该混为一谈。

第二,清洗和整形都不是一次性任务,而是需要反复迭代、验证和审计的工程过程。每一步处理都要有依据、可追踪,并且保留原始数据作为对照。

第三,Airbnb Listings 数据集是练习数据清洗和整形的优质材料,因为它几乎包含了所有真实数据的典型问题。把这一套方法吃透之后,迁移到电商订单、招聘信息、房产挂牌等数据上,只需要调整字段名和业务规则即可。

后续可以从这几个方向继续深入:

  • 把清洗和整形结果用于探索性数据分析,比如研究纽约不同区域房源的定价差异。
  • 结合 scikit-learn 构建价格预测模型,理解缺失值填充方式对模型效果的影响。
  • 学习更高级的数据整形工具,比如pandaspipe链式调用、polars的高性能数据框操作。

建议收藏这篇文章,等真正拿到一份 Airbnb Listings 数据时,照着流程走一遍,再把清洗函数改造成适合自己业务的版本。数据清洗没有那么多“银弹”,多做几次真实数据,比看一百篇教程都管用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询