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_pathmeta参数,一步到位展开。具体用法可以查官方文档,我这里就不展开了。

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提供了几个非常实用的方法,我每次都会跑一遍:

  1. df.head() —— 看前5行,确认数据长什么样
  2. df.info() —— 看列名、非空数量、数据类型
  3. df.describe() —— 看数值列的统计摘要
  4. df['category'].value_counts() —— 看类别分布
  5. 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 知识体系:数据加载与初探的核心逻辑

为了让你更直观地理解这一章的内容结构,我画了一张流程图。它展示了从原始标注文件到数据概览的完整路径:

数据加载与初探核心流程 原始标注文件 格式判断 JSON格式 CSV格式 json.load() + json_normalize() pd.read_csv() + dtype指定 数据概览(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的读取方法掌握好,再养成做数据概览的习惯,后面做可视化分析就会顺畅很多。记住一句话:数据质量决定了分析的天花板。这一步花的时间,后面都会加倍还给你。