跳到内容

使用结构化输出将凌乱的表格转换为整洁的数据

为什么这是个问题?

凌乱的数据导出是一个常见问题。无论是表格中的多个标题、使分析变得困难的隐式关系,还是仅仅合并的单元格,使用带有结构化输出的 instructor 都可以轻松地将凌乱的表格转换为整洁的数据,即使你只有表格的图片,正如我们下面将看到的。

让我们以下表为例。它通过空单元格和隐式重复隐藏了数据关系,这使得分析变得不必要地困难。如果我们在进行数据分析时遇到这样的表格,手动清理将是一场巨大的噩梦。

例如,受试者 ID (321) 和 GTT 日期只出现在第一行,下面的空白单元格暗示这些值适用于后续行。这种格式破坏了大多数 pandas 操作——你无法简单地按受试者 ID 分组或与其他数据集合并,除非进行复杂的预处理来填充这些缺失值。

取而代之的是,时间序列测量值分布在多行中,胰岛素列中存在混合数据类型(数字和“lo off curve”),并且通过空单元格隐藏了重复的受试者信息。这意味着即使是像计算按时间点划分的平均葡萄糖水平或绘制葡萄糖曲线这样的简单操作,也需要进行数据重塑并仔细处理缺失/特殊值。

使用结构化输出

定义自定义类型

使用 instructor 等工具将非整洁数据自动转换为整洁格式,可以节省数小时的预处理时间,并减少分析流程中的错误。

我们首先定义一个自定义类型,它可以将 markdown 表格解析为 pandas 数据框。

from io import StringIO
from typing import Annotated, Any
from pydantic import BeforeValidator, PlainSerializer, InstanceOf, WithJsonSchema
import pandas as pd


def md_to_df(data: Any) -> Any:
    # Convert markdown to DataFrame
    if isinstance(data, str):
        return (
            pd.read_csv(
                StringIO(data),  # Process data
                sep="|",
                index_col=1,
            )
            .dropna(axis=1, how="all")
            .iloc[1:]
            .applymap(lambda x: x.strip())
        )
    return data


MarkdownDataFrame = Annotated[
    InstanceOf[pd.DataFrame],
    BeforeValidator(md_to_df),
    PlainSerializer(lambda df: df.to_markdown()),
    WithJsonSchema(
        {
            "type": "string",
            "description": "The markdown representation of the table, each one should be tidy, do not try to join tables that should be separate",
        }
    ),
]

提取表格

然后,利用这个新的自定义数据类型,就可以轻松地将图片传递给 LLM 并获得一个整洁的数据框作为响应。

import instructor
from pydantic import BaseModel
from openai import OpenAI


class Table(BaseModel):
    caption: str
    dataframe: MarkdownDataFrame  # Custom type for handling tables


class TidyTables(BaseModel):
    tables: list[Table]


# Patch the OpenAI client with instructor
client = instructor.from_openai(OpenAI())


def extract_table(image_path: str) -> TidyTables:
    return client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "user",
                "content": [
                    "Convert this untidy table to tidy format",
                    instructor.Image.from_path(image_path),
                ],
            }
        ],
        response_model=TidyTables,
    )


extracted_tables = extract_table("./untidy_table.png")

这会为我们返回以下输出,它是一个单一的 pandas 数据框,我们可以轻松地对其进行绘图和任何类型的数据分析。

ID GTT 日期 GTT 体重 时间 葡萄糖 mg/dl 胰岛素 ng/ml 评论
321 2/9/15 24.5 0 99.2 lo off curve
321 2/9/15 24.5 5 349.3 0.205
321 2/9/15 24.5 15 286.1 0.129
321 2/9/15 24.5 30 312 0.175
321 2/9/15 24.5 60 99.9 0.122
321 2/9/15 24.5 120 217.9 lo off curve
322 2/9/15 18.9 0 185.8 0.251
322 2/9/15 18.9 5 297.4 2.228
322 2/9/15 18.9 15 439 2.078
322 2/9/15 18.9 30 362.3 0.775
322 2/9/15 18.9 60 232.7 0.5
322 2/9/15 18.9 120 260.7 0.523
323 2/9/15 24.7 0 198.5 0.151
323 2/9/15 24.7 5 530.6 off curve lo

更重要的是,我们还可以从一张图片中提取多个表格。这有助于分割和识别凌乱报告的不同部分。使用整洁的数据,我们可以获得以下好处:

  1. 每个变量作为一列
  2. 每个观测值作为一行
  3. 每个值拥有自己的单元格
  4. 与 pandas/numpy 操作无缝协作
  5. 可视化库“直接可用”

结论

我们实际上可以更进一步,通过将体重、葡萄糖和胰岛素等项转换为一个名为“指标”(metric)的特定列来使其更加整洁,这样我们就可以向表格添加任意指标,而无需更改模式或绘图代码。这在进行复杂数据分析时能极大地提高效率。

告别繁杂的数据清洗流程。让模型处理繁重的工作,而你专注于分析。有了 instructor,迈出这一步变得更加容易。

今天就尝试 instructor,看看如何构建可靠的应用程序。只需运行 pip install instructor 或查看我们的入门指南