数据准备:读取与清洗家庭用电数据

好,咱们正式开始动手了。上一章我们聊了电费账单分析的全局思路,这一章,我来带你搞定最基础也最关键的一步——把数据从CSV文件里读进来,然后洗干净。

你可能觉得这步没啥技术含量。嗯,我以前也这么想。直到有一次,我跑完整个分析模型,发现结果异常——回头一查,原来是数据里混了几条「2023年2月30日」的记录。你说气不气人?从那以后,数据清洗我从来不敢马虎。

2.1 读取CSV文件

家庭用电数据,最常见的就是CSV格式。每家每户的电表数据,通常包含这几列:时间戳、用电量(千瓦时)、电压、电流、功率因数等。

我习惯用 pandasread_csv() 来读。为什么?因为它的参数设计太贴心了,几乎覆盖了所有现实中的坑。

import pandas as pd

# 读取家庭用电数据
df = pd.read_csv(
    'household_electricity.csv',
    parse_dates=['timestamp'],   # 自动解析时间列
    infer_datetime_format=True,  # 自动推断时间格式
    encoding='utf-8'             # 避免中文乱码
)

print(f'数据加载完成,共 {df.shape[0]} 行,{df.shape[1]} 列')
df.head()
小技巧: 如果你遇到编码报错,试试 encoding='gbk'encoding='gb2312'。我在南方某小区的数据里就遇到过这种情况,因为他们的电表系统是老款,导出的CSV是GBK编码。

2.2 初步探索数据

数据读进来了,别急着往下走。先看看它长什么样。我一般会做三件事:

  • 看列名和类型——确认每一列是不是我们想要的
  • 看缺失值——哪些列有空值,占比多少
  • 看基本统计量——用电量的最小值、最大值、平均值,有没有异常
# 1. 查看数据基本信息
print('=== 数据概览 ===')
print(df.info())

print('\n=== 缺失值统计 ===')
print(df.isnull().sum())

print('\n=== 数值列统计描述 ===')
print(df.describe())

这里有个坑,我踩过好几次——describe() 默认只统计数值列。如果你的用电量列是字符串类型(比如带了单位「kWh」),它就不会被统计到。所以,记得先确认数据类型。

2.3 数据清洗四步走

数据清洗,说白了就是「去脏、补缺、纠错、去重」。我总结了一个四步流程,你照着做基本不会出问题。

2.3.1 处理缺失值

缺失值怎么处理?要看情况。如果缺失比例很小(比如不到5%),我倾向于直接删除。如果缺失比较多,就得考虑填充了。

# 删除缺失比例超过50%的列
threshold = len(df) * 0.5
df = df.dropna(thresh=threshold, axis=1)

# 对用电量列,用前后均值填充
df['power_consumption'] = df['power_consumption'].fillna(
    df['power_consumption'].interpolate()
)

# 对时间戳列,直接删除缺失行
df = df.dropna(subset=['timestamp'])
注意: 我曾经在一个项目中,直接用均值填充了所有缺失值,结果导致模型偏差很大。后来发现,那些缺失值其实集中在某个特定时间段(比如深夜),均值填充反而掩盖了真实规律。所以,填充前一定要分析缺失模式。

2.3.2 处理重复数据

重复数据很常见。电表有时候会重复上报同一时刻的数据。我的做法是:

# 检查重复行
duplicates = df.duplicated(subset=['timestamp']).sum()
print(f'发现 {duplicates} 条重复的时间戳记录')

# 删除重复,保留第一条
df = df.drop_duplicates(subset=['timestamp'], keep='first')

2.3.3 处理异常值

异常值是最头疼的。比如用电量突然飙到10000千瓦时——这明显是电表故障。我一般用「四分位距法」来识别:

# 用电量列的异常值检测
Q1 = df['power_consumption'].quantile(0.25)
Q3 = df['power_consumption'].quantile(0.75)
IQR = Q3 - Q1

lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

outliers = df[
    (df['power_consumption'] < lower_bound) | 
    (df['power_consumption'] > upper_bound)
]
print(f'检测到 {len(outliers)} 条异常用电记录')

# 将异常值替换为上下限
df['power_consumption'] = df['power_consumption'].clip(
    lower=lower_bound, upper=upper_bound
)
经验之谈: 别一看到异常值就删。有一次我发现某户人家用电量连续几天异常高,后来一查,原来是家里新装了电动汽车充电桩。这种「异常」其实是真实行为,应该保留。

2.3.4 时间序列对齐

家庭用电数据通常是15分钟或1小时一条。但实际数据里,时间间隔可能不均匀。我习惯把它重采样成固定频率:

# 设置时间戳为索引
df = df.set_index('timestamp')

# 重采样为1小时间隔,用均值填充
df_hourly = df.resample('1H').mean()

# 前向填充缺失的小时段
df_hourly = df_hourly.fillna(method='ffill')

print(f'重采样完成,共 {len(df_hourly)} 条小时级数据')

2.4 知识体系总览

下面这张图,是我自己总结的数据准备流程。你看一眼,心里就有谱了。

数据准备与清洗流程 读取CSV read_csv() 初步探索 info() / describe() 数据清洗 缺失/重复/异常 对齐 resample 缺失值处理 重复值删除 异常值检测 时间对齐 清洗后的数据 → 可用于后续分析与建模 干净数据

2.5 完整代码示例

最后,我把上面所有步骤串成一个完整的函数。你以后处理类似数据,直接复制改改就能用。

def load_and_clean_electricity_data(filepath):
    """
    加载并清洗家庭用电数据
    参数:
        filepath: CSV文件路径
    返回:
        清洗后的DataFrame
    """
    # 1. 读取
    df = pd.read_csv(filepath, parse_dates=['timestamp'])
    
    # 2. 删除缺失过多的列
    df = df.dropna(thresh=len(df)*0.5, axis=1)
    
    # 3. 填充用电量缺失值
    df['power_consumption'] = df['power_consumption'].interpolate()
    
    # 4. 删除重复时间戳
    df = df.drop_duplicates(subset=['timestamp'])
    
    # 5. 异常值处理
    Q1 = df['power_consumption'].quantile(0.25)
    Q3 = df['power_consumption'].quantile(0.75)
    IQR = Q3 - Q1
    lower = Q1 - 1.5 * IQR
    upper = Q3 + 1.5 * IQR
    df['power_consumption'] = df['power_consumption'].clip(lower, upper)
    
    # 6. 时间对齐
    df = df.set_index('timestamp').resample('1H').mean().ffill()
    
    return df

# 使用示例
clean_df = load_and_clean_electricity_data('household_electricity.csv')
print(f'清洗完成,剩余 {len(clean_df)} 条有效数据')
我的习惯: 每次清洗完数据,我都会把清洗前后的行数、列数、缺失率打印出来,存到一个日志文件里。这样万一后面分析出问题,我能快速定位是不是清洗环节出了岔子。

好了,数据准备好了。下一章,我们就可以开始真正的分析工作了。记住一句话:数据清洗花的时间,永远会在后续分析中加倍还给你。


专注资料整理