Day 49:【99天精通Python】Pandas 进阶 - 数据清洗与合并
前言
欢迎来到第49天!
在现实世界中,我们拿到的数据从来都不是完美的。它们可能:
- 缺胳膊少腿:某些单元格是空的 (
NaN)。 - 脏乱差:格式不统一(日期写成文本),包含重复行。
- 四分五裂:数据分散在多张表里(用户信息表、订单表)。
数据分析师 80% 的时间都在做数据清洗 (Data Cleaning)。只有把数据洗干净了,后续的分析和模型才有意义(Garbage In, Garbage Out)。
本节内容:
- 处理缺失值 (
isnull,dropna,fillna) - 处理重复值 (
duplicated,drop_duplicates) - 数据类型转换 (
astype) - 多表连接 (
merge) - 数据合并 (
concat) - 实战练习:电商订单数据清洗
一、缺失值处理 (Missing Data)
在 Pandas 中,缺失值通常显示为NaN(Not a Number) 或None。
importpandasaspdimportnumpyasnp# 创建一个包含缺失值的 DataFramedf=pd.DataFrame({"Name":["Tom","Jerry","Spike",None],"Score":[88,np.nan,75,60],"City":["Beijing","Shanghai",np.nan,"Beijing"]})print(df)1.1 检测缺失值
print(df.isnull())# 返回布尔表 (True 表示缺失)print(df.isnull().sum())# 统计每列缺失值的数量 (非常常用!)# Name 1# Score 1# City 11.2 删除缺失值 (Drop)
# 只要有一列是 NaN,整行删除df_clean=df.dropna()# 只有全行都是 NaN 才删除df_clean_all=df.dropna(how='all')# 删除 Score 列缺失的行 (subset 指定列)df_score_clean=df.dropna(subset=['Score'])1.3 填充缺失值 (Fill)
删除太可惜了,我们通常会用默认值或平均值填充。
# 1. 用固定值填充 (所有 NaN 变 0)df_filled=df.fillna(0)# 2. 用字典针对不同列填充values={"Name":"Unknown","Score":df["Score"].mean(),"City":"Unknown"}df_filled_smart=df.fillna(value=values)print(df_filled_smart)二、重复值处理
data={"Name":["Tom","Tom","Jerry"],"Age":[10,10,8]}df=pd.DataFrame(data)# 1. 检查重复 (返回布尔 Series)print(df.duplicated())# 2. 删除重复 (保留第一条)df_unique=df.drop_duplicates()print(df_unique)三、数据类型转换
经常遇到读进来的数字是字符串类型(比如 “100”),这会导致无法计算。
df=pd.DataFrame({"Price":["100","200","300"]})# 查看类型print(df.dtypes)# Price object (即字符串)# 转换类型 (astype)df["Price"]=df["Price"].astype(int)print(df.dtypes)# Price int32四、数据合并 (Merge & Concat)
这是 SQL 用户最熟悉的操作。
4.1 Merge (SQL Join)
根据某一列(键)将两张表左右拼接。
# 用户表df_users=pd.DataFrame({"user_id":[1,2,3],"name":["Alice","Bob","Charlie"]})# 订单表df_orders=pd.DataFrame({"order_id":[101,102,103],"user_id":[1,1,2],# user_id 是外键"amount":[50,100,200]})# 内连接 (Inner Join): 只保留两边都有的merged=pd.merge(df_orders,df_users,on="user_id",how="inner")print(merged)# order_id user_id amount name# 0 101 1 50 Alice# 1 102 1 100 Alice# 2 103 2 200 Bob# (注意:Charlie 没有订单,所以不在结果里)# 左连接 (Left Join): 保留左表所有数据merged_left=pd.merge(df_orders,df_users,on="user_id",how="left")4.2 Concat (拼接)
简单的上下堆叠(增加行)。
df1=pd.DataFrame({"A":[1,2]})df2=pd.DataFrame({"A":[3,4]})# 上下拼接 (axis=0)result=pd.concat([df1,df2],ignore_index=True)print(result)# A# 0 1# 1 2# 2 3# 3 4五、实战练习:电商数据清洗
假设我们有一份脏数据raw_data.csv:
id,product,price,sales 1,Apple,5.0,100 2,Banana,3.5, 3,Apple,5.0,100 4,Orange,,50 5,Grape,10.0,20任务:
- 读取数据。
- 去除重复行。
- 填充缺失的
sales(用 0 填充)。 - 删除
price缺失的行。 - 计算总销售额。
代码实现
importpandasaspdfromioimportStringIO# 模拟读取 CSV (为了方便演示,直接用字符串)csv_data="""id,product,price,sales 1,Apple,5.0,100 2,Banana,3.5, 3,Apple,5.0,100 4,Orange,,50 5,Grape,10.0,20 """df=pd.read_csv(StringIO(csv_data))print("---"" 原始数据 ---"")print(df)# 1. 去重 (排除 id 列,只看内容是否重复)# keep='first': 保留第一个df=df.drop_duplicates(subset=['product','price','sales'])# 2. 填充 sales (NaN -> 0)df['sales']=df['sales'].fillna(0)# 3. 删除 price 缺失的行df=df.dropna(subset=['price'])# 4. 计算销售额# 确保类型正确 (以防万一)df['price']=df['price'].astype(float)df['sales']=df['sales'].astype(int)df['revenue']=df['price']*df['sales']print("\n---"" 清洗后数据 ---"")print(df)print(f"\n总销售额:{df['revenue'].sum()}")六、常见问题
Q1:fillna(inplace=True)是什么意思?
很多 Pandas 方法(如fillna,drop)默认不会修改原数据,而是返回一个新的 DataFrame。
- 写法1:
df = df.fillna(0)(推荐,逻辑清晰) - 写法2:
df.fillna(0, inplace=True)(直接修改 df,不返回)
Q2:merge时列名不一样怎么办?
如果是左表的uid和右表的user_id连接:
pd.merge(df_left,df_right,left_on="uid",right_on="user_id")七、小结
关键要点:
- 拿到数据先看
df.info()和df.isnull().sum()。 - 缺失值处理三板斧:删(
dropna)、填(fillna)、插值。 - 多表关联用
merge,一定要搞清是 Inner 还是 Left Join。
八、课后作业
- 用户数据清洗:创建一个 DataFrame,包含
["Name", "Age", "Email"]。- 把 Age 为空的行填为平均年龄。
- 把 Email 为空的行删除。
- 把 Age 列转换为整数类型。
- 成绩合并:有两个 CSV,
math.csv(学号, 数学分) 和english.csv(学号, 英语分)。请读取并用merge将它们合并成一张总表 (学号, 数学, 英语)。 - 异常值检测 (挑战):在练习1的基础上,假如 Age 中混入了 200 岁的数据,请编写代码将其识别为异常值并剔除(比如保留 0-100 岁的数据)。
下节预告
Day 50:数据可视化 Matplotlib 基础- 数据洗干净了,终于可以画图了!明天我们系统学习 Matplotlib,绘制折线图、散点图和饼图。
系列导航:
- 上一篇:Day 48 - 数据分析Pandas入门
- 下一篇:Day 50 - 数据可视化Matplotlib基础(待更新)