第1章:数据获取与清洗——用Pandas读取CSV/Excel环境数据,处理缺失值与异常值

大家好,我是老张。干环境数据分析这行十几年了,今天咱们聊聊数据获取与清洗。说实话,很多新手一上来就急着建模、跑预测,结果模型跑出来一塌糊涂。为什么?因为数据本身就有问题。你想想看,垃圾数据进去,再牛的算法也白搭。

我个人习惯,拿到任何环境数据,第一件事不是分析,而是先看看数据长什么样、有没有坑。今天这章,我就把这些年踩过的坑、总结的经验,一次性讲清楚。

1.1 环境数据的常见来源与格式

环境数据来源五花八门。我接触过的就有:

  • 气象站数据:温度、湿度、风速、气压,通常是CSV格式
  • 空气质量监测站:PM2.5、PM10、SO₂、NO₂,Excel或CSV居多
  • 水文监测数据:水位、流量、水质指标,有时是数据库导出的文本文件
  • 遥感数据:卫星影像反演的地表温度、植被指数,格式更复杂

咱们课程主要用CSV和Excel。为什么?因为这两种格式最常见,也最容易上手。我在项目中遇到过,有些合作方给的数据是Excel里套着Excel,一个文件几十个sheet,光找数据就花了半天。嗯,这种情况咱们后面会讲怎么处理。

1.2 使用Pandas读取CSV文件

Pandas是Python里处理表格数据的利器。读取CSV,一行代码搞定:

import pandas as pd

# 读取CSV文件
df = pd.read_csv('air_quality_beijing.csv')
print(df.head())  # 看看前5行

但实际项目中,CSV文件往往没那么规矩。我遇到过编码问题、分隔符问题、表头问题。来看几个常见场景:

问题 解决方案 代码示例
文件编码是GBK 指定encoding参数 pd.read_csv('data.csv', encoding='gbk')
分隔符是分号 指定sep参数 pd.read_csv('data.csv', sep=';')
没有表头 设置header=None pd.read_csv('data.csv', header=None)
跳过前几行注释 设置skiprows pd.read_csv('data.csv', skiprows=2)
我的小技巧: 读取大文件时,先读前100行看看格式,没问题再全量读取。用 nrows=100 参数就行。

1.3 读取Excel文件

Excel文件比CSV复杂一些,因为一个文件可能有多个sheet。我建议这样操作:

# 读取Excel文件
df = pd.read_excel('water_quality.xlsx', sheet_name='监测数据')

# 如果不知道sheet名字,先看看有哪些sheet
xls = pd.ExcelFile('water_quality.xlsx')
print(xls.sheet_names)

# 读取所有sheet
all_sheets = pd.read_excel('water_quality.xlsx', sheet_name=None)

这里有个坑。我曾经处理过一个Excel文件,里面有些单元格是合并的,读进来全是NaN。后来发现,合并单元格在Pandas里默认只保留左上角的值,其他位置都是空值。解决办法是提前在Excel里取消合并,或者用 fillna(method='ffill') 向前填充。

1.4 数据概览与基本信息

数据读进来了,先别急着分析。花30秒看看数据的基本情况:

# 查看数据形状
print(f"数据有 {df.shape[0]} 行,{df.shape[1]} 列")

# 查看列名和数据类型
print(df.dtypes)

# 查看统计摘要
print(df.describe())

# 查看缺失值情况
print(df.isnull().sum())

我个人习惯,拿到数据先看三样东西:数据量够不够有没有缺失值数据类型对不对。比如时间列应该是datetime类型,如果读进来是object,那后面做时间序列分析就会出问题。

注意: 环境数据的时间列经常是字符串格式。记得用 pd.to_datetime() 转换,否则后续的resample、shift等操作都会报错。

1.5 处理缺失值

环境数据里缺失值太常见了。仪器故障、网络中断、人为漏记,原因五花八门。处理缺失值,我一般分三步走:

1.5.1 查找缺失值

# 查看每列缺失值数量
missing = df.isnull().sum()
print(missing[missing > 0])

# 查看缺失值占比
missing_pct = df.isnull().mean() * 100
print(missing_pct[missing_pct > 0])

1.5.2 决定处理策略

缺失值怎么处理,取决于数据的重要性和缺失比例。我常用的策略:

  • 缺失比例 < 5%:直接删除,用 df.dropna()
  • 缺失比例 5%-20%:用均值/中位数/前向填充
  • 缺失比例 > 20%:需要慎重,可能要考虑插值或模型预测
# 删除缺失值(谨慎使用)
df_clean = df.dropna()

# 用均值填充
df['PM2.5'] = df['PM2.5'].fillna(df['PM2.5'].mean())

# 前向填充(时间序列常用)
df['温度'] = df['温度'].fillna(method='ffill')

# 线性插值
df['风速'] = df['风速'].interpolate(method='linear')
经验之谈: 时间序列数据,我强烈推荐用前向填充或插值。为什么?因为环境变化是连续的,前一小时的值往往比全局均值更有参考价值。我曾经用均值填充处理PM2.5数据,结果模型预测偏差很大,后来换成前向填充,效果好了不少。

1.6 处理异常值

异常值比缺失值更隐蔽,也更危险。它不会报错,但会悄悄扭曲你的分析结果。我见过最离谱的,一个温度传感器坏了,连续三天输出-999,有人没处理直接拿去建模,结果预测值全是负数。

1.6.1 识别异常值的方法

常用的方法有三种:

方法 适用场景 代码示例
Z-score法 数据近似正态分布 z = np.abs(stats.zscore(df['PM2.5']))
IQR四分位距法 数据偏态分布 Q1, Q3 = df['温度'].quantile([0.25, 0.75])
领域知识法 有明确的物理范围 df[(df['风速'] >= 0) & (df['风速'] <= 50)]
# IQR法示例
Q1 = df['温度'].quantile(0.25)
Q3 = df['温度'].quantile(0.75)
IQR = Q3 - Q1

# 定义异常值范围
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

# 找出异常值
outliers = df[(df['温度'] < lower_bound) | (df['温度'] > upper_bound)]
print(f"发现 {len(outliers)} 个异常值")

1.6.2 处理异常值的策略

找到异常值后,怎么处理?我的建议是:

  • 明显错误值(比如负数的PM2.5):直接删除或设为NaN
  • 极端但可能真实的值(比如沙尘暴时的PM2.5爆表):保留,但要标记
  • 不确定的异常值:用前后值的均值替换
曾经踩过的坑: 有一次我处理某城市的气温数据,7月份出现了-5°C的记录。按IQR法判断是异常值,但我查了历史天气,那天确实有寒潮。所以,别盲目相信统计方法,一定要结合领域知识判断。

1.7 数据清洗的完整流程

说了这么多,咱们来总结一下。我一般按这个流程走:

# 完整的数据清洗流程
def clean_environment_data(df):
    # 1. 备份原始数据
    df_raw = df.copy()
    
    # 2. 转换时间列
    df['时间'] = pd.to_datetime(df['时间'])
    df = df.set_index('时间')
    
    # 3. 处理缺失值(前向填充)
    df = df.fillna(method='ffill')
    
    # 4. 处理异常值(IQR法)
    for col in df.select_dtypes(include=[np.number]).columns:
        Q1 = df[col].quantile(0.25)
        Q3 = df[col].quantile(0.75)
        IQR = Q3 - Q1
        lower = Q1 - 3 * IQR  # 用3倍IQR,更宽松
        upper = Q3 + 3 * IQR
        df[col] = df[col].clip(lower, upper)
    
    # 5. 检查最终数据
    print(f"清洗完成,剩余 {len(df)} 条记录")
    return df

这个流程我用了好几年,基本能覆盖90%的环境数据问题。当然,具体项目还要具体调整。

1.8 本章知识体系

下面这张图,是我画的数据获取与清洗的核心流程。你看一眼,心里就有数了:

数据获取与清洗核心流程 数据获取 CSV / Excel / 数据库 数据概览 形状 / 类型 / 统计摘要 缺失值处理 删除 / 填充 / 插值 异常值处理 Z-score / IQR / 领域知识 数据转换 时间格式 / 数据类型 清洗完成 可用于时间序列分析 数据清洗是时间序列分析的基础,这一步做扎实了,后面事半功倍

这张图把咱们今天讲的内容串起来了。从数据获取开始,到概览、缺失值、异常值、数据转换,最后得到干净的数据。每一步都有对应的Pandas方法,你照着做就行。

1.9 写在最后

数据清洗这件事,说白了就是「磨刀不误砍柴工」。我见过太多人急着跑模型,结果被脏数据坑得怀疑人生。你花30分钟把数据洗干净,后面建模可能省3个小时。这笔账,你自己算算。

下一章,咱们会讲时间序列数据的可视化。到时候,你会看到清洗前后的数据,画出来的图完全是两个世界。嗯,今天就到这儿,有问题随时交流。


公众号:蓝海资料掘金营,微信deep3321