3. 数据加载与初探:读取JSON/CSV格式的标注文件,使用Pandas进行数据概览
好,咱们正式开始动手了。前面聊了那么多理论,现在终于要跟真实的数据打交道。我个人觉得,这一步是整个可视化流程里最容易被低估的环节——很多人上来就画图,结果图出来了才发现数据有问题,白忙活一场。
说白了,数据加载和初探,就是给你的标注数据做个「入职体检」。你得先搞清楚它长什么样、有没有缺胳膊少腿、字段对不对得上。今天咱们就聊聊怎么用Python把JSON和CSV这两种最常见的标注文件读进来,再用Pandas快速摸清底细。
3.1 标注文件的两种主流格式
先说说格式。我在项目中遇到过不少团队,明明用的是同一个标注平台,导出格式却五花八门。但归根结底,主流就两种:JSON和CSV。
- JSON:结构灵活,适合嵌套数据。比如一张图里有多个目标框,每个框又有类别、坐标、属性,用JSON存就很自然。标注平台(Labelme、CVAT、Label Studio)默认导出基本都是JSON。
- CSV:表格结构,简单直观。适合标注字段固定、层级不深的情况。比如文本分类、情感标注,一行一个样本,列名就是字段名。
你想想看,如果你的标注数据是目标检测的,一张图对应多个框,用CSV存就得把框信息拆成多行,或者用逗号拼成一个字段——这两种做法都不太优雅。所以我的建议是:嵌套结构用JSON,平铺结构用CSV。
核心原则:标注文件的格式选择,取决于你的数据是「一对多」还是「一对一」。一对多(一张图多个标注)优先JSON,一对一(一个样本一个标签)优先CSV。
3.2 用Pandas读取JSON文件
Pandas读取JSON其实比很多人想象的要灵活。它不只是读一个简单的键值对,还能处理嵌套结构。咱们来看一个典型的标注JSON长什么样:
{
"images": [
{
"id": 1,
"file_name": "img_001.jpg",
"width": 1920,
"height": 1080,
"annotations": [
{"category": "car", "bbox": [100, 200, 300, 400]},
{"category": "person", "bbox": [500, 600, 100, 200]}
]
}
]
}
这种结构,直接用pd.read_json()读进来,你会发现它把整个文件当成一个字典,images字段是一个列表。嗯,这里要注意:Pandas默认不会自动展开嵌套。
我习惯的做法是分两步走:
import pandas as pd
import json
# 第一步:用json库读原始数据
with open('annotations.json', 'r') as f:
data = json.load(f)
# 第二步:把images列表展开成DataFrame
df_images = pd.json_normalize(data['images'])
# 第三步:把annotations嵌套字段也展开
df_annotations = df_images.explode('annotations').reset_index(drop=True)
df_annotations = pd.concat([
df_annotations.drop('annotations', axis=1),
pd.json_normalize(df_annotations['annotations'])
], axis=1)
print(df_annotations.head())
你看,这样就把嵌套的标注信息拆成了平铺的表格。每一行对应一个标注框,同时保留了图片的元信息。我在做自动驾驶标注数据清洗时,这个套路用了不下十次,非常稳。
小技巧:如果JSON嵌套层级特别深(比如标注属性里还有子属性),可以用pd.json_normalize()的record_path和meta参数,一步到位展开。具体用法可以查官方文档,我这里就不展开了。
3.3 用Pandas读取CSV文件
CSV就简单多了。但简单不代表没坑。我曾经接手过一个项目,CSV文件里有一列的数值明明应该是整数,结果读进来全是字符串——原因是标注员在某个单元格里不小心打了个空格。
标准读取方式:
df = pd.read_csv('annotations.csv')
print(df.info()) # 先看数据类型对不对
这里我强烈建议你养成一个习惯:读CSV时永远加上dtype参数。比如:
dtype_dict = {
'image_id': int,
'category': str,
'x_min': float,
'y_min': float,
'width': float,
'height': float
}
df = pd.read_csv('annotations.csv', dtype=dtype_dict)
为什么要这么做?因为Pandas默认会自己推断类型,但一旦遇到脏数据(比如空值、混入的文本),它就会把整列降级成object类型。你后面做数值计算时就会报错,排查起来很头疼。
避坑指南:我曾经因为没指定dtype,导致一个项目的标注框面积计算全部出错——因为宽度和高度被读成了字符串,相加时变成了拼接。排查了整整两个小时才发现问题。从那以后,我读CSV必写dtype。
3.4 数据概览:快速摸清标注数据的底细
数据读进来了,接下来就是「摸家底」。Pandas提供了几个非常实用的方法,我每次都会跑一遍:
df.head()—— 看前5行,确认数据长什么样df.info()—— 看列名、非空数量、数据类型df.describe()—— 看数值列的统计摘要df['category'].value_counts()—— 看类别分布df.isnull().sum()—— 看缺失值情况
咱们用一个实际例子来演示。假设你有一个目标检测的标注CSV,包含1000张图片、5000个标注框:
# 读取数据
df = pd.read_csv('detection_annotations.csv')
# 1. 数据概览
print("=== 前5行 ===")
print(df.head())
print("\n=== 基本信息 ===")
print(df.info())
print("\n=== 数值统计 ===")
print(df.describe())
print("\n=== 类别分布 ===")
print(df['category'].value_counts())
print("\n=== 缺失值检查 ===")
print(df.isnull().sum())
输出结果会告诉你很多信息。比如:
- 如果
bbox_width的最小值是负数,那说明标注数据有问题 - 如果某个类别的样本数只有个位数,那模型训练时可能会欠拟合
- 如果缺失值太多,你得决定是填充还是删除
我个人习惯把这些信息整理成一个表格,方便后续跟团队沟通:
| 检查项 | 正常范围 | 异常处理 |
|---|---|---|
| 图片数量 | ≥ 标注任务要求 | 缺少则联系标注团队补标 |
| 标注框坐标 | x_min < x_max, y_min < y_max | 坐标异常则剔除或修正 |
| 类别分布 | 各类别样本数均衡 | 不均衡则考虑重采样 |
| 缺失值比例 | < 5% | 超过则评估是否影响分析 |
3.5 知识体系:数据加载与初探的核心逻辑
为了让你更直观地理解这一章的内容结构,我画了一张流程图。它展示了从原始标注文件到数据概览的完整路径:
这张图其实就概括了咱们这一章的全部内容。你从原始文件出发,先判断格式,然后选择对应的读取方式,最后做5步数据概览。每一步都有对应的代码和注意事项,照着走基本不会出错。
3.6 实战小贴士
最后分享几个我在实际项目中积累的经验:
- 大文件处理:如果标注文件超过几百MB,别直接用
pd.read_json(),它会爆内存。我一般用chunksize参数分批读取,或者先用json.load()配合迭代器。 - 编码问题:中文标注文件经常遇到编码错误。读CSV时加上
encoding='utf-8'或encoding='gbk',哪个不行换哪个。 - 备份原始数据:永远不要直接在原始文件上修改。我习惯先读进来,然后
df.to_csv('backup.csv')存一份,再开始清洗。
我的习惯:每次拿到新的标注数据,我会先写一个data_inspection.py脚本,把上面说的5步检查自动化。这样每次只需要改文件路径,就能快速生成一份数据质量报告。省时省力,还能避免遗漏。
好了,数据加载和初探就聊到这儿。你只要把JSON和CSV的读取方法掌握好,再养成做数据概览的习惯,后面做可视化分析就会顺畅很多。记住一句话:数据质量决定了分析的天花板。这一步花的时间,后面都会加倍还给你。