引言:为什么数据修改是数据分析的"隐形冠军"?
在真实项目中,原始数据往往存在以下问题:
- 缺失值用
-1或NULL填充 - 日期格式混乱(如
20230101vs01-Jan-2023) - 分类变量编码不一致(如
男/M/1表示同一性别)
数据修改 (Data Mutation)是解决这些问题的核心能力,它直接影响后续分析的准确性。本文将通过 6类操作 + 4个实战案例,系统讲解如何安全、高效地修改DataFrame数据。
一、基础修改操作:替换与赋值
1. 单单元格修改
直接通过列名和索引定位单元格:
python
import pandas as pd
df = pd.DataFrame({
'Name': ['Alice', 'Bob', 'Charlie'],
'Age': [25, 30, None],
'City': ['NY', 'LA', 'Chicago']
})
# 将第2行(索引1)的Age改为35
df.at[1, 'Age'] = 35 # 推荐:at用于标量访问
# 或
df.loc[1, 'Age'] = 35 # loc也可用
2. 整列修改
直接对列赋值(支持标量、列表、Series):
python
# 将所有City改为大写
df['City'] = df['City'].str.upper()
# 用新列表替换整列
df['Age'] = [26, 35, 32] # 长度必须匹配
3. 条件替换(where() vs mask())
df.where(cond, other):保留 满足条件的值,不满足的替换为otherdf.mask(cond, other):替换 满足条件的值为other,保留不满足的
python
# 将Age<30的替换为0(保留其他值)
df['Age'] = df['Age'].where(df['Age'] >= 30, 0)
# 等价写法(更直观)
df['Age'] = df['Age'].mask(df['Age'] < 30, 0)
二、批量修改技巧:映射与替换
1. replace():精确值替换
python
# 将City中的'NY'替换为'New York'
df['City'] = df['City'].replace('NY', 'New York')
# 多值替换(字典形式)
df['City'] = df['City'].replace({
'LA': 'Los Angeles',
'CHICAGO': 'Chicago' # 注意大小写敏感
})
2. map():基于字典的映射
适用于分类变量编码转换:
python
# 创建性别编码映射
gender_map = {'男': 'M', '女': 'F', '未知': 'U'}
df['Gender'] = df['Gender'].map(gender_map)
# 未映射的值会变为NaN,可用fillna处理
df['Gender'] = df['Gender'].map(gender_map).fillna('Other')
3. apply():自定义函数修改
当逻辑复杂时,用lambda或函数:
python
# 将Age列转换为字符串并添加' years'
df['Age'] = df['Age'].apply(lambda x: f"{int(x)} years" if pd.notnull(x) else 'Unknown')
# 多列同时修改
def clean_data(row):
row['Name'] = row['Name'].title() # 首字母大写
row['City'] = row['City'].upper()
return row
df = df.apply(clean_data, axis=1) # axis=1表示按行操作
三、进阶修改场景
1. 基于索引的修改
python
# 修改索引为[0,2]的行的Age列
df.loc[[0, 2], 'Age'] = [27, 33]
# 使用切片修改连续行
df.loc[0:2, 'City'] = ['NYC', 'LAX', 'CHI'] # 注意包含末端
2. 添加/删除列时的修改
python
# 添加新列(基于现有列计算)
df['Age_Group'] = pd.cut(df['Age'], bins=[0, 30, 100], labels=['Young', 'Senior'])
# 删除列后重新添加(重置数据)
df.drop('Age_Group', axis=1, inplace=True)
df['Age_Group'] = ... # 重新计算
3. 链式操作(Chaining)
结合多个修改步骤(注意可读性):
python
df = (
df.assign(Age_Squared=lambda x: x['Age']**2) # 添加新列
.query("Age > 20") # 筛选
.drop('City', axis=1) # 删除列
)
四、实战案例:电商数据清洗
案例1:统一价格单位
原始数据中价格单位混用(元/千元):
python
df = pd.DataFrame({
'Product': ['A', 'B', 'C'],
'Price': [1500, 2.5, 800],
'Unit': ['元', '千元', '元']
})
# 将千元转换为元
df['Price'] = df.apply(
lambda row: row['Price'] * 1000 if row['Unit'] == '千元' else row['Price'],
axis=1
)
df['Unit'] = '元' # 统一单位
案例2:标准化日期格式
python
df = pd.DataFrame({
'OrderID': [1001, 1002],
'Date': ['20230115', '15-Jan-2023']
})
# 统一转换为YYYY-MM-DD
df['Date'] = pd.to_datetime(df['Date']).dt.strftime('%Y-%m-%d')
案例3:缺失值填充策略
python
# 根据列特性选择填充方式
df['Age'].fillna(df['Age'].median(), inplace=True) # 数值列用中位数
df['Gender'].fillna('Unknown', inplace=True) # 分类列用众数或固定值
五、避坑指南:5个常见错误
-
修改时未用
copy()导致SettingWithCopyWarning❌ 错误写法:
df[df['Age']>30]['City'] = 'Old'✅ 正确写法:
df.loc[df['Age']>30, 'City'] = 'Old' -
链式操作中隐式复制
python# 危险!可能修改的是副本而非原数据 subset = df[df['Age']>30]['City'] subset[:] = 'Old' -
忽略数据类型转换
python# 字符串列无法直接参与数学运算 df['Price'] = df['Price'].astype(float) # 先转换类型 -
过度使用
apply()对于简单操作,优先用向量化方法(如
str.upper()比apply(lambda x: x.upper())快10倍)。 -
未处理
inplace参数python# 明确是否修改原数据 df.drop('Col', axis=1) # 未生效!需加inplace=True或赋值 df = df.drop('Col', axis=1) # 推荐写法
总结:数据修改的"黄金法则"
- 先查看再修改 :用
df.head()和df.info()确认数据结构 - 优先向量化:避免逐行操作的低效
- 备份原数据 :修改前用
df_original = df.copy() - 验证结果 :通过
df.describe()和抽样检查修改是否生效
下一步建议:
- 学习
pd.cut()和pd.qcut()实现分箱操作 - 探索
sklearn.preprocessing中的标准化/归一化方法 - 掌握
DataFrame.update()实现基于另一个DataFrame的修改
附:完整代码与数据集
GitHub仓库链接(可替换为实际链接)
希望这篇博客能让你彻底告别"想改数据却不敢改"的困境!如果有任何疑问,欢迎在评论区讨论 💬