数据读取与初检函数:进门先看货
做质控分析的第一步是什么?不是建模,不是画图,而是把数据稳稳当当读进来。我记得早些年带新人时,最常见的问题反而不是分析逻辑不对,而是数据都读歪了。
所以这一章,咱们专门聊聊数据读取和初检。说白了,就是把文件从硬盘请进Python,再快速扫一眼——“这数据到底能不能用?”
常见格式数据读取:别让文件格式卡住你
实际项目中,数据源什么妖魔鬼怪都有。我碰过最离谱的一次,客户发来一个 .csv,打开一看里面用逗号分割,但某个字段里又夹了逗号,解析直接崩溃。从那以后,我习惯把文件读取函数统一封装起来,加一道格式校验。
常用的读取函数,我一般会封装成下面这个样子:
import pandas as pd
import json
def load_data(file_path, file_type=None):
if file_type is None:
ext = file_path.split('.')[-1].lower()
else:
ext = file_type.lower()
loaders = {
'csv': lambda: pd.read_csv(file_path),
'xlsx': lambda: pd.read_excel(file_path),
'json': lambda: pd.read_json(file_path),
'parquet': lambda: pd.read_parquet(file_path),
'feather': lambda: pd.read_feather(file_path),
}
if ext not in loaders:
raise ValueError(f"不支持的格式:{ext}")
return loaders[ext]()
你问我为啥不直接一行 pd.read_csv?嗯,封装的好处是:一旦哪天你遇上加密 Excel 或 GBK 编码的 CSV,直接在 loaders 里加一句 cleaning 逻辑就行,不用改业务代码。
def detect_encoding(file_path):
import chardet
with open(file_path, 'rb') as f:
result = chardet.detect(f.read(10000))
return result['encoding']
数据概览函数封装:一眼看出问题
读取完了,我习惯立刻调一个“快速扫描”函数。为什么?直接跑分析容易翻车,先看概览心里才有底。
def quick_overview(df):
info = {}
info['shape'] = df.shape
info['columns'] = list(df.columns)
info['dtypes'] = df.dtypes.astype(str).to_dict()
info['missing_count'] = df.isnull().sum().to_dict()
info['missing_ratio'] = (df.isnull().sum() / len(df)).to_dict()
info['duplicate_rows'] = df.duplicated().sum()
info['memory_usage'] = df.memory_usage(deep=True).sum() / 1024**2
return info
missing_ratio。如果某列缺失率 > 80%,直接考虑扔掉,别浪费时间填充。
我还见过一个同事,每次分析前只瞄一眼 shape 就开始建模,结果跑了一上午,最后发现标签列全是空值。白忙活。
数据类型自动推断:拯救“全是 object”的噩梦
有没有遇到过这种尴尬:读进来的 DataFrame,所有列的类型都是 object?
我之前调试过一个案例,销售日期列是 object,导致按时间聚合时全部报错。原因就是 Excel 里日期格式不统一,pandas 推断失败了。所以我现在都会加一道类型自动纠正。
def auto_infer_types(df):
for col in df.columns:
# 尝试转数字
try:
df[col] = pd.to_numeric(df[col], errors='ignore')
except:
pass
# 尝试转时间
try:
df[col] = pd.to_datetime(df[col], errors='ignore')
except:
pass
# 如果object列的唯一值少于某阈值,转为分类
if df[col].dtype == 'object':
if df[col].nunique() < len(df) * 0.05:
df[col] = df[col].astype('category')
return df
你可能会说,pandas 不是有 infer_objects() 吗?没错,但那个太温柔了,很多隐蔽的类型问题它放过去了。我的这个版本,暴力一点,但管用。
基础统计量计算函数:给数据做个体检
开始分析前,我会打一套“组合拳”——连续型变量看分布,离散型变量看频数。
def basic_stats(df):
results = {}
numeric_cols = df.select_dtypes(include=['int64', 'float64']).columns
if len(numeric_cols) > 0:
stats = df[numeric_cols].describe().T
stats['skew'] = df[numeric_cols].skew()
stats['kurtosis'] = df[numeric_cols].kurtosis()
stats['missing'] = df[numeric_cols].isnull().sum()
results['numeric'] = stats
categorical_cols = df.select_dtypes(include=['object', 'category']).columns
cat_stats = {}
for col in categorical_cols:
cat_stats[col] = {
'unique_count': df[col].nunique(),
'top_values': df[col].value_counts().head(5).to_dict(),
'missing': int(df[col].isnull().sum())
}
results['categorical'] = cat_stats
return results
我曾经接手过一个让人头疼的项目,里面有一个特征叫“客户评分”,范围是1-5。我习惯性地算了一下偏度,发现 skew = 0.02,非常完美。但我是怎么发现的呢?——分布全是4分和5分,几乎没有1-3分。偏度骗不了人,但描述性统计里最好配上百分比分布。
来看看我常用的一组决策逻辑:
| 指标 | 正常值参考 | 可能的问题 |
|---|---|---|
| 缺失率 | < 5% | > 20% 需评估列是否可用 |
| 偏度 | -1 ~ 1 | 过偏需检查异常值或取对数 |
| 唯一值数量 | 合理范围内 | ID列或错误编码 |
| 最大值/最小值 | 符合业务常识 | 越界数据(如年龄500) |
min_check 和 max_check 的边界条件。
本章核心知识结构
我把这一套“读取 → 概览 → 推断 → 统计”的流程画了一张图,方便你记忆:
看完这张图你应该明白了:整个初检流程其实就是一个过滤器,从原始文件一直到质控报告,每一层都在降低数据风险。
data_precheck.py 的模块里,项目一来,直接 import,十分钟内就能对数据有个整体判断。省下来的时间,足够你好好喝杯咖啡了。