目录

一、数据的导入与导出

1. CSV 文件的导入与导出

2. Excel 文件的导入与导出

3. JSON 文件的导入与导出

二、缺失值处理

1. 缺失值处理方法

2. 检测缺失值 (isna / isnull)

3. 删除缺失值 (dropna)

4. 填充缺失值 (fillna)

三、重复值处理

1. 重复值处理方法汇总

2. 检测重复行 (duplicated)

3. 删除重复行 (drop_duplicates)

四、数据类型

1. 数据类型处理汇总

2. 基础数据准备

3. 强制类型转换 (astype)

4. 转换为日期时间

5. 转换为分类数据

6. 数值格式化

总结


一、数据的导入与导出

数据清洗的第一步是 "取数" 与 "存数"。Pandas 提供了强大且一致的 I/O(输入/输出)API,常见的语法模式为:

  • 读取数据:pd.read_<format>()

  • 写出数据:df.to_<format>()

这一节我们将重点梳理最常见的 CSVExcelJSON 三种格式的操作


1. CSV 文件的导入与导出

CSV 是数据分析中最通用的文本表格格式,体积小且加载速度快。

(1) 读取 CSV 文件

import pandas as pd

# 1. 基础读取
df_csv = pd.read_csv('sales_data.csv')

# 2. 带有常用参数的高级读取
df_csv = pd.read_csv(
    'sales_data.csv',
    sep=',',               # 分隔符
    encoding='utf-8',      # 文件编码格式
    index_col='User_ID',   # 指定哪一列作为 DataFrame 的行索引
    usecols=['User_ID', 'Age', 'Amount'],  # 仅读取指定的列,减少内存占用
    nrows=1000             # 仅读取前 1000 行
)

(2) 保存到 CSV 文件

# 将数据保存至本地 CSV
df_csv.to_csv(
    'sales_cleaned.csv',
    index=False,           # 通常设为 False,避免将自增数字索引保存为额外的一列
    encoding='utf-8-sig'  
)

2. Excel 文件的导入与导出

Excel(.xlsx / .xls)是办公中最普遍的数据载体。Pandas 依赖底层引擎来高效读写 Excel 文件

(1) 读取 Excel 文件

# 1. 基础读取
df_excel = pd.read_excel('financial_report.xlsx')

# 2. 读取指定 Sheet 与特定范围
df_excel = pd.read_excel(
    'financial_report.xlsx',
    sheet_name='2023_Q4',  # 可传入 Sheet 名称,也可传入索引(如 0 代表第一个 Sheet)
    header=0,              # 指定第几行为列名(默认为 0)
    skiprows=2             # 跳过前 2 行无用的标题或元数据行
)

(2) 保存到 Excel 文件

# 保存到 Excel 文件
df_excel.to_excel(
    'financial_report_cleaned.xlsx',
    sheet_name='Cleaned_Data',
    index=False            # 不导出行索引
)

如果需要将多个 DataFrame 分别写入同一个 Excel 文件的不同 Sheet 中,可以使用 pd.ExcelWriter() 上下文管理器:

with pd.ExcelWriter('multi_sheets.xlsx') as writer:
    df1.to_excel(writer, sheet_name='Sheet1', index=False)
    df2.to_excel(writer, sheet_name='Sheet2', index=False)

3. JSON 文件的导入与导出

JSON 是网络 API 接口交互与半结构化数据存储的通用格式

(1) 读取 JSON 文件

# 读取 JSON 文件或网络接口数据
df_json = pd.read_json('user_profiles.json')

# 处理嵌套或特定结构的 JSON (通过 orient 参数设置解析结构)
df_json = pd.read_json('user_profiles.json', orient='records')

(2) 保存到 JSON 文件

# 将 DataFrame 导出为 JSON 格式
df_json.to_json(
    'user_profiles_out.json',
    orient='records',      # 常用格式:[{col1: val1, col2: val2}, ...]
    force_ascii=False,     # 确保中文字符不被编码为 \uXXXX 格式
    indent=4               # 设置缩进,方便人类阅读
)

关键细节

  1. 中文乱码问题

    • 在 Windows 环境下读取本地文件时,如果提示 UnicodeDecodeError,可以将 encoding 参数切换为 'gbk' 或 'gb2312'

    • 用 to_csv() 保存带有中文的数据并使用 Excel 打开时,强烈建议指定 encoding='utf-8-sig',Excel 才能正确识别 BOM 标记避免乱码

  2. Unnamed: 0 冗余列问题

    • 如果在导出的文件没有设置 index=False,下次用 read_csv() 重新读入时,Pandas 会把上次保存的自增索引当作普通列读入,并命名为 Unnamed: 0。设置 index=False 即可解决这一常见痛点

二、缺失值处理

在真实业务数据集中,由于系统传输故障、用户拒填或传感器异常等原因,数据缺失是不可避免的。在 Pandas 中,缺失值通常用 NaN 或 Python 原生的 None 来表示

在统计计算前,我们必须先识别缺失值,再结合业务逻辑选择删除填充


1. 缺失值处理方法

下表汇总了 Pandas 中处理缺失值(NaN)的最核心 API:

方法 / 操作 语法示例 描述
检测缺失值 df.isna () 或 df.isnull () 返回布尔矩阵,标记元素是否为缺失值(NaN 或 None)
统计缺失值 df.isna().sum() 按列统计每列包含的缺失值总数
删除缺失值 df.dropna() 默认删除包含任意缺失值的行(axis=0)
删除缺失值 df.dropna(axis=1) 删除包含任意缺失值的列
删除缺失值 df.dropna(subset=['col1']) 仅根据指定列的缺失情况来删除行
填充缺失值 df.fillna(value) 用固定值填充(如 df.fillna (0))
填充缺失值 df.fillna(method='ffill') 用前一个非缺失值填充(向前填充,新版推荐 df.ffill ())
填充缺失值 df.fillna(method='bfill') 用后一个非缺失值填充(向后填充,新版推荐 df.bfill ())
填充缺失值 df.fillna(df.mean()) 使用各数值列的均值(或中位数 / 众数)自动填充

2. 检测缺失值 (isna / isnull)

我们构建一份包含不同程度缺失的示例数据,演示检测、删除与填充的全过程:

import pandas as pd
import numpy as np

# 构建包含缺失值的示例 DataFrame
data = {
    'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eva'],
    'Age': [25, np.nan, 35, np.nan, 28],
    'Score': [90, 85, np.nan, 95, 88],
    'Dept': ['HR', 'IT', 'IT', None, 'HR']
}

df = pd.DataFrame(data)

  • isna() 与 isnull():两者功能完全相同,均会返回一个与原表同等形状的布尔值矩阵,缺失值为 True,正常值为 False

  • isna().sum():由于布尔值在 Python 中可以当作 1 和 0 计算,结合 .sum() 就能快速得到每列缺失值的数量

# 1. 检测缺失值矩阵
print("--- 缺失值布尔矩阵 ---")
print(df.isna())

# 2. 统计每列缺失值的数量
print("\n--- 每列缺失值统计 ---")
print(df.isna().sum())

输出结果:


3. 删除缺失值 (dropna)

如果缺失数据占比较低且无法准确还原,或者某关键字段缺失会导致后续分析无效,可以直接采用删除策略:

# 1. 默认删除:只要行内有任意一个 NaN,就将该行整行删除
df_drop_default = df.dropna()
print("--- (df.dropna()) ---")
print(df_drop_default)

# 2. 按列删除:只要列内包含 NaN,就将整列删除
df_drop_col = df.dropna(axis=1)
print("\n--- df.dropna(axis=1) ---")
print(df_drop_col)

# 3. 仅根据特定列进行条件删除
# 场景:只有当 'Age' 列缺失时才删除该行
df_drop_subset = df.dropna(subset=['Age'])
print("\n--- df.dropna(subset=['Age']) ---")
print(df_drop_subset)

输出结果:


4. 填充缺失值 (fillna)

删除数据可能会丢失有价值的信息。大多数情况下,我们会根据业务逻辑填充缺失值:

(1) 使用固定值填充

# 1. 全表用固定值填充 (例如用 0)
df_zero = df.fillna(0)

# 2. 针对特定列用不同的固定值填充
df_custom = df.fillna({
    'Age': 0,
    'Score': 60,
    'Dept': 'Unassigned'
})

print(df_custom)

输出结果:

(2) 向前 / 向后填充

  • ffill (forward fill):用缺失位置上一个非空值向上/向前覆盖填充

  • bfill (backward fill):用缺失位置下一个非空值向下/向后覆盖填充

# 用上一个非空值填充 (ffill)
df_ffill = df.ffill()

print("--- 前向填充 (ffill) ---")
print(df_ffill)

输出结果:

(3) 使用统计指标填充(均值、中位数、众数)

针对数值型数据,最常用的填补手段是用该列的均值或中位数

# 复制副本
df_stat = df.copy()

# 用 Age 列的均值填充 Age 的缺失值
mean_age = df_stat['Age'].mean()  # (25 + 35 + 28) / 3 = 29.33
df_stat['Age'] = df_stat['Age'].fillna(mean_age)

# 用 Dept 列的众数填充 Dept 的缺失值
mode_dept = df_stat['Dept'].mode()[0]  # 'HR' 和 'IT' 中选出最高频元素
df_stat['Dept'] = df_stat['Dept'].fillna(mode_dept)

print(df_stat)

输出结果:

关键细节

  1. isna() 与 isnull() 的关系

    • 在 Pandas 源码中,isnull() 仅仅是 isna() 的别名,二者完全等价。选用 isna() 命名更符合现代数据科学规范

  2. 伪缺失值问题

    • 有时导入的数据包含字符型的 "N/A"、"NULL"、"None" 或空白符 " ",Pandas 默认可能无法将其识别为 NaN。此时需要在读取数据时指定参数,或者先调用 replace 将其标准化为 NaN 后再统一处理

  3. Pandas 2.0 语法变更

    • 在旧版本中常见的 df.fillna(method='ffill') 语法在最新版本中已被弃用,强烈建议直接改用 df.ffill() 和 df.bfill(),代码更简洁

三、重复值处理

在数据采集、多次系统接口对接或表格合并拼接的过程中,数据集中经常会出现重复录入的脏数据。重复数据不仅会虚增样本总量,还会严重偏离均值、总量等统计分析结果

在 Pandas 中,我们主要使用 duplicated() 进行重复识别,使用 drop_duplicates() 进行去重清洗


1. 重复值处理方法汇总

下表汇总了重复值检测与删除的核心 API 及常见组合用法:

语法示例 描述
df.duplicated() 返回布尔序列标记重复行(默认将首次出现的行标记为 False,后续重复出现的标记为 True)
df.drop_duplicates() 保留首次出现的行,删除其余重复行(默认检查所有列)
df.drop_duplicates
(subset=['col1'])
仅根据指定列进行去重
df.drop_duplicates(keep='last') 保留最后一次出现的行,删除之前的重复行
df.drop_duplicates(keep=False) 删除所有存在重复的行(一次都不保留)

2. 检测重复行 (duplicated)

为了清晰演示全列重复与局部列重复的区别,我们构建以下示例数据集:

import pandas as pd

data = {
    'User_ID': [101, 102, 101, 103, 102],
    'Name': ['Alice', 'Bob', 'Alice', 'Charlie', 'Bob'],
    'Score': [90, 85, 90, 95, 88]  # 注意:User 102 两次记录的 Score 不同
}

df = pd.DataFrame(data)

df.duplicated() 会返回一个与原 DataFrame 等长的布尔类型 Series:

  • 默认机制:对于完全相同的行,第一次出现的记录返回 False(视作非重复),从第二次出现开始返回 True(标记为重复)

  • 快速计数:结合 .sum() 可以直接算出整张表中有多少行重复记录

# 1. 检查全列完全重复的行
dup_series = df.duplicated()
print("--- 检测完全重复行 ---")
print(dup_series)

# 2. 统计重复行总数
dup_count = df.duplicated().sum()
print(f"\n全表完全重复的行数: {dup_count}")

# 3. 提取所有重复的记录进行查看
print("\n--- 提取重复的行内容 ---")
print(df[df.duplicated()])

输出结果:


3. 删除重复行 (drop_duplicates)

(1) 默认去重

默认情况下,df.drop_duplicates() 会检查所有列,只有当整行各个字段完全一致时才会删除:

# 默认去重 (保留第 0 行,删除第 2 行)
df_clean = df.drop_duplicates()
print(df_clean)

输出结果:

(2) 指定关键列去重 (subset)

在业务分析中,我们经常需要依据业务主键(如 User_ID)进行去重。即使其他字段存在差异,只要主键相同就视为重复:

# 仅根据 'User_ID' 列进行去重
df_sub_clean = df.drop_duplicates(subset=['User_ID'])
print(df_sub_clean)

输出结果:

说明:

索引 4 的 (102, Bob, 88) 虽然 Score 是 88(与索引 1 的 85 不同),但由于设置了 subset=['User_ID'],Pandas 识别到 User_ID=102 已经出现过,因此索引 4 被顺利去除

(3) 保留策略控制 (keep)

通过设置 keep 参数,我们可以精准控制去重时到底留下哪一条:

  • keep='first' (默认):保留首次出现的记录

  • keep='last':保留最后一次出现的记录

  • keep=False:只要有重复,一条都不留

# 1. 保留最后一次出现的记录
df_keep_last = df.drop_duplicates(subset=['User_ID'], keep='last')

# 2. 剔除所有包含重复的项
df_keep_none = df.drop_duplicates(subset=['User_ID'], keep=False)

print("--- keep='last' ---")
print(df_keep_last)

print("\n--- keep=False ---")
print(df_keep_none)

输出结果:

关键细节

  1. keep=False 排查重复数据: 如果想把所有涉及重复的记录全挑出来对比分析,可以组合使用 df[df.duplicated(subset=['ID'], keep=False)],这样所有重复的行(包括第 1 次出现的原行)都会被完整筛选出来,方便人工比对审查

  2. 去重前排序: 如果你想按某个时间戳字段保留最新的一条数据,最稳妥的写法是:

    # 先按时间升序排序,再保留最后一条 (或降序排序后保留第一条)
    df_latest = (df.sort_values('Update_Time').
                 drop_duplicates(subset=['User_ID'], keep='last'))
    
  3. 字符串空格影响: 有时候看似一模一样的文本(如 ' Alice' 与 'Alice'),因为带有隐蔽的前后空格,duplicated() 会将其认定为不重复。在去重前对字符串列先执行 .str.strip() 往往能避免这种问题

四、数据类型

在将外部数据导入 Pandas 时,Pandas 会尝试自动推断每列的数据类型。然而,由于源文件中可能存在脏数据、空值或格式不统一,列类型经常会出现错配——例如:原本是数字的年龄被读成了字符串,原本是日期的字段被当作普通文本存储

如果数据类型不正确,不仅无法调用对应的特有方法,还会极大地浪费内存


1. 数据类型处理汇总

下表汇总了 Pandas 中查看、转换与格式化数据类型的最常用 API:

方法 / 操作 语法 描述
查看数据类型 df.dtypes 显示每列的数据类型
强制类型转换 df['col'].astype('int') 将列转换为指定类型
转换为日期时间 pd.to_datetime(df['col']) 将字符串或数值列转为 datetime 类型
转换为分类数据 df['col'].astype('category') 将列转为分类类型
数值格式化 df['col'].round(2) 保留指定小数位数(如保留 2 位小数)

2. 基础数据准备

我们构建一份包含多种类型错配问题的数据集:

import pandas as pd
import numpy as np

# 构建存在类型错配的示例 DataFrame
data = {
    'User_ID': ['1001', '1002', '1003', '1004'],        # 应该是整数,但被读作了字符串
    'Age': ['25', '30', '35', '40'],                    # 应该是数值,但被读作了字符串
    'Salary': [8500.5678, 12000.1234, 9800.0, 15000.8888],# 小数位数过长,需格式化
    'Gender': ['Male', 'Female', 'Male', 'Female'],      # 取值有限,适合转为分类类型
    'Join_Date': ['2023-01-15', '2023/02/20', '20230305', '2023-04-10'] # 格式不统一的日期文本
}

df = pd.DataFrame(data)

print("--- 原始数据类型 ---")
print(df.dtypes)

输出结果:


3. 强制类型转换 (astype)

使用 astype() 可以将列转换为指定的数据类型,如 int64, float64, str 等。也可以一次性通过字典批量转换多列:

# 1. 单列转换:将 Age 列从 object 转为 int
df['Age'] = df['Age'].astype(int)

# 2. 字典批量转换:将 User_ID 转为 int,Age 转为 float
df = df.astype({
    'User_ID': int,
    'Age': float
})

print("--- astype 转换后的类型 ---")
print(df.dtypes[['User_ID', 'Age']])

输出结果:


4. 转换为日期时间

普通文本类型的日期(如 '2023-01-15')无法进行时间加减、按季度统计等操作。通过 pd.to_datetime() 可以识别各种格式的日期并统一转为 Pandas 标准的 datetime64 类型:

# 将格式各异的日期文本统一转换为 datetime 类型
df['Join_Date'] = pd.to_datetime(df['Join_Date'], format='mixed')

print("--- 转换后的 Join_Date 列 ---")
print(df['Join_Date'])
print("\n类型:", df['Join_Date'].dtype)

# 转换后即可轻松提取年份、月份、星期等时间属性
df['Join_Year'] = df['Join_Date'].dt.year
df['Join_Month'] = df['Join_Date'].dt.month

输出结果:


5. 转换为分类数据

对于重复出现且唯一取值较少的字符串列(如:性别、省份、学历、订单状态等),将其转换为 category(分类) 类型不仅能大幅节省内存开销,还能加快后续分组汇总的运算性能:

# 转换前查看内存占用
mem_before = df['Gender'].memory_usage(deep=True)

# 将 Gender 转换为 category 分类类型
df['Gender'] = df['Gender'].astype('category')

# 转换后查看内存占用
mem_after = df['Gender'].memory_usage(deep=True)

print(f"转换前内存占用: {mem_before} bytes")
print(f"转换后内存占用: {mem_after} bytes")
print("Gender 列类型:", df['Gender'].dtype)

输出结果:

原理:转换为 category 后,Pandas 底层只会将独特的分类文本存储一次,列内部使用高效的整数编码代替重复的长字符串,在百万级数据量下效果极其显著


6. 数值格式化

对于浮点数列,如果小数位数过多,可以使用 .round(decimals) 精确保留指定的小数位数:

# 将 Salary 保留 2 位小数
df['Salary'] = df['Salary'].round(2)

print("--- Salary 格式化保留 2 位小数 ---")
print(df[['User_ID', 'Salary']])

输出结果:

关键细节

1. 含有 NaN 缺失值的列直接转 int 会报错:

  • 在标准 NumPy/Pandas 中,原生 int 类型不支持存储 NaN。如果一列包含缺失值,执行 df['col'].astype(int) 会直接抛出 ValueError

  • 解决方案 A:改用 Pandas 提供的可空整数类型 Int64(首字母大写):df['col'].astype('Int64')

  • 解决方案 B:先填充缺失值 df['col'].fillna(0) 后再转 astype(int)

2. 包含非非法字符时使用 pd.to_numeric:

  • 如果数值列里混入了字母或非法字符(如 '100', '200', 'Unknown'),直接用 astype(float) 会报错。此时可以使用安全转换 API:pd.to_numeric(df['col'], errors='coerce'),非法字符会被自动替换为 NaN 而不会中断程序

总结

本章学习了 Pandas 中常见的数据清洗与预处理方法,包括数据的导入与导出、缺失值处理、重复数据处理以及数据类型转换等内容,并结合实际案例掌握了常用清洗函数的使用方法。数据清洗是数据分析过程中不可或缺的一步,良好的数据质量能够为后续分析提供可靠保障

下一篇将继续学习 Pandas 数据变形与时间数据处理,包括数据重塑、文本处理、日期时间处理以及时间序列等内容,进一步提升数据整理与转换能力

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐