数据格式与读取:CSV、Excel、JSON
做质控模块,第一道坎不是算法,是数据读取。我见过太多人花大把时间调模型,结果连数据都读不对——编码错、列名乱、类型搞混。说实话,这比写逻辑还让人头大。
今天我们就来搞定三种最常用的格式:CSV、Excel 和 JSON。这三种格式,基本覆盖了生物信息、工业检测和数据分析里 90% 的场景。
4.1 CSV:最基础也最容易翻车
CSV 看起来简单吧?不就是逗号分隔嘛。但我跟你说,恰恰是因为它“简单”,所以坑最多。我在一个基因测序项目里遇到过,某个实验室导出的 CSV,逗号和引号混用,还带着 BOM 头——结果 pandas 读出来第一列全是乱码。
4.1.1 pandas 读 CSV 的标准套路
我个人习惯用 pandas 的 read_csv(),参数多但可控。你想想看,如果只是用 csv 模块一行行解析,碰上大文件效率还行,但做数据集整合还是 pandas 方便。
import pandas as pd
def read_csv_qc(filepath, **kwargs):
"""
读取 CSV 并做基础质控
"""
default_params = {
'sep': ',',
'encoding': 'utf-8',
'header': 0,
'dtype': str, # 先全读成字符串,避免类型推断翻车
'keep_default_na': False,
'low_memory': False
}
default_params.update(kwargs)
try:
df = pd.read_csv(filepath, **default_params)
print(f'[OK] 读取完成:{filepath}')
print(f' 行数:{len(df)},列数:{len(df.columns)}')
return df
except Exception as e:
print(f'[FAIL] 读取失败:{e}')
return None
dtype=str 是我自己的习惯。为什么呢?因为质控阶段我们更关心“数据有没有被正确读到”,而不是“它是不是数字”。等清洗完再转类型,心里更有底。
4.1.2 避坑指南:常见的 CSV 幺蛾子
- 编码问题:UTF-8、UTF-8 BOM、GBK、Latin-1 混着来。我建议先试 UTF-8,报错就切
encoding='utf-8-sig'或encoding='latin-1' - 引号包裹的字段:某些字段里有逗号,比如 "Smith, John" 整体是一个值。用
quotechar='"'搞定 - 空行和注释行:有些 CSV 文件开头有 # 打头的注释,用
comment='#'自动跳过 - 列数不一致:某一行多了一个逗号,导致列数对不上。这时候
error_bad_lines=False(旧版)或on_bad_lines='skip'(新版 pandas)可以跳过
# 我曾经遇到过的真实场景:CSV 元数据混合
data_sample = '''# 批次:2024-03-15 质控数据
# 操作员:李工
样本ID,浓度,纯度,备注
QC001,12.3,98.5,"合格"
QC002,9.8,95.2,待复测
QC003,15.1,97.8,合格
'''
# 用参数组合处理这种混合格式
df = pd.read_csv(
io.StringIO(data_sample),
comment='#',
quotechar='"',
encoding='utf-8'
)
4.2 Excel:功能强但吃内存
Excel 是目前工业界最主流的质控数据交换格式。说实话,它的优势在于非技术人员也能直接看。但劣势也很明显——一个 10 万行的 Excel 文件,能把你内存吃到怀疑人生。
4.2.1 引擎选择:openpyxl vs xlrd
pandas 读 Excel 依赖后端引擎。我强烈建议你直接装 openpyxl,因为它支持 .xlsx,而且对公式、合并单元格的处理更稳定。
| 引擎 | 文件格式支持 | 性能 | 推荐场景 |
|---|---|---|---|
| openpyxl | .xlsx | 中等,大文件较慢 | 日常质控、中小文件 |
| calamine | .xlsx / .xls / .xlsb | 快,Rust 实现 | 大文件、跨格式场景 |
| xlrd | 仅 .xls | 较快 | 遗留系统 .xls 文件 |
def read_excel_qc(filepath, sheet_name=0, **kwargs):
"""
读取 Excel,自动选择引擎
"""
default_params = {
'engine': 'openpyxl',
'header': 0,
'dtype': str
}
default_params.update(kwargs)
# 如果文件超过 50MB,建议用 calamine
import os
fsize = os.path.getsize(filepath)
if fsize > 50 * 1024 * 1024:
print('[WARN] 文件超过 50MB,推荐使用 calamine 引擎')
default_params['engine'] = 'calamine'
try:
df = pd.read_excel(filepath, sheet_name=sheet_name, **default_params)
print(f'[OK] 读取完成:{filepath} > sheet: {sheet_name}')
print(f' 行数:{len(df)},列数:{len(df.columns)}')
return df
except Exception as e:
print(f'[FAIL] 读取失败:{e}')
return None
pd.read_excel(..., header=None) 后自己拼表头。
4.2.2 多 Sheet 读取策略
批次质控经常是一个批次一个 sheet,或者一个批次日期一个 sheet。用 sheet_name=None 可以一次读完所有 sheet,返回一个 dict。
# 读取所有 sheet
all_sheets = pd.read_excel('batch_qc_data.xlsx', sheet_name=None, engine='openpyxl')
for sheet_name, df in all_sheets.items():
print(f'Sheet: {sheet_name} - 数据行数: {len(df)}')
4.3 JSON:嵌套结构的读写
JSON 现在越来越常见,尤其是从 API 接口获取的质控结果。为什么用 JSON?因为它可以表达复杂的嵌套关系。比如一个样本的完整质控链路:提取→扩增→测序→分析,每个步骤都有子结果。
4.3.1 标准 JSON 读取
最简单的就是 pd.read_json()。但有一个关键点——如果 JSON 是嵌套的,直接读可能会展开成多层索引,索引是用元组表示的。我个人更倾向于先读成 Python 字典,再决定怎么展平。
import json
import pandas as pd
# 一个典型的批次质控 JSON
batch_json = '''
{
"batch_id": "B20240315",
"samples": [
{"id": "QC001", "concentration": 12.3, "purity": 98.5, "status": "PASS"},
{"id": "QC002", "concentration": 9.8, "purity": 95.2, "status": "REVIEW"}
],
"meta": {
"operator": "李工",
"instrument": "NanoDrop-2000"
}
}
'''
# 方式一:直接 read_json(适用于较规整的 JSON)
df = pd.read_json(batch_json, orient='records')
# 方式二:先 load 再处理(灵活度高)
data = json.loads(batch_json)
df = pd.DataFrame(data['samples'])
print(df)
read_json() 的结果不对劲,可以先 json.loads() 然后把结果打印出来看。这比猜参数快得多。
4.3.2 处理深层嵌套
我遇到过最夸张的是一个三层嵌套的 JSON——样本信息包着实验数据,实验数据里又包着质控指标。这个场景下 json_normalize() 是救星。
# 嵌套 JSON 示例
nested_json = {
"batch": "B01",
"samples": [
{
"id": "S001",
"qc_metrics": {
"concentration": 15.2,
"purity_260_280": 1.85,
"integrity": {"rqn": 9.5, "method": "RNAScreen"}
}
}
]
}
# 用 json_normalize 展平(pandas 1.3+ 推荐 pd.json_normalize)
from pandas import json_normalize
records = json_normalize(
nested_json['samples'],
sep='_',
max_level=2
)
print(records.columns.tolist())
# 输出:['id', 'qc_metrics.concentration', 'qc_metrics.purity_260_280', 'qc_metrics.integrity.rqn', 'qc_metrics.integrity.method']
4.3.3 Schema 验证与一致性检查
批次质控的 JSON 经常来自不同系统。我曾经踩过一个大坑——上游系统突然改了字段名,把 purity 改成了 purity_260_280,我们完全没发现,结果后续分析全错了。
从那以后,我写了一个简单的 schema 校验函数:
def validate_qc_json(data, required_fields):
"""
验证 JSON 是否包含必要的质控字段
required_fields: list of strings, 支持点分隔
"""
def check_path(obj, path):
keys = path.split('.')
current = obj
for key in keys:
if isinstance(current, dict) and key in current:
current = current[key]
else:
return False
return True
missing = [field for field in required_fields if not check_path(data, field)]
if missing:
print(f'[WARN] 缺少字段: {missing}')
return False
return True
# 使用示例
schema = ['batch_id', 'samples', 'samples.id', 'samples.concentration']
if not validate_qc_json(data, schema):
raise ValueError('JSON 结构不符合质控 schema,请检查数据来源')
4.4 三种格式的取舍与总结
做质控模块这么多年,我自己的经验是:
- CSV:适合原始仪器输出、大规模日志。占空间小,但缺少元数据表达能力
- Excel:适合研发团队内部协作、汇报。非技术人员可以直接看,但大文件要小心
- JSON:适合系统间接口传输、复杂嵌套的质控链路。可读性不如 Excel,但程序处理方便
嗯,说到底,数据读取这步做好了,后面所有的统计、建模、可视化才有意义。别嫌基础,我见过太多“分析结果对不上原始数据”的教训,十有八九都是第一步读数据时埋下的雷。