2. 量子测量结果的数据结构

量子测量实验的原始产出往往是一串“事件”:每次测量在某个设置下得到一个结果标签。单次事件本身信息有限,真正支撑统计推断的是把这些事件按测量设置与结果标签分组后得到的计数与概率估计。本节要解决的核心问题是:如何用 Python 字典与 Pandas DataFrame 把实验记录组织成一张可计算、可复查、可扩展的统计表。

三个基本字段:设置、标签、计数

一条完整的测量记录至少需要三个语义层次。第一层是测量设置,它描述“在什么条件下测的”,例如测量基(Z 基、X 基)、比特编号、脉冲参数或探测器配置。第二层是结果标签,即该次测量实际读出的离散结果,如 0、1,或多比特情形下的 00、01、10、11。第三层是计数,即同一(设置,标签)组合出现的次数。概率估计则由计数除以该设置下的总次数得到。

这里必须明确一个约定:概率估计的分母是同一测量设置下的总计数,而不是全部实验的总计数。不同设置(如 Z 基与 X 基)的结果分布不可混在一起归一化,否则会得到无物理意义的概率。这个约定在后续所有代码与图示中保持一致。

用字典承载原始记录

最贴近实验现场的表示是“记录列表”,每条记录是一个字典,字段名直接对应实验语义。下面是一段示例数据(数值为教学假设,非实测结果):

records = [
    {"basis": "Z", "qubit": 0, "label": "0"},
    {"basis": "Z", "qubit": 0, "label": "0"},
    {"basis": "Z", "qubit": 0, "label": "1"},
    {"basis": "X", "qubit": 0, "label": "0"},
    {"basis": "X", "qubit": 0, "label": "1"},
    {"basis": "X", "qubit": 0, "label": "1"},
]

这种结构的优点是忠实保留每次事件,便于追溯与重算;缺点是逐条统计效率低。因此实践中常先聚合成“嵌套字典”:外层键为设置,内层键为标签,值为计数。

from collections import Counter, defaultdict

counts = defaultdict(Counter)
for r in records:
    counts[(r["basis"], r["qubit"])][r["label"]] += 1

# counts[("Z", 0)] -> Counter({"0": 2, "1": 1})
# counts[("X", 0)] -> Counter({"0": 1, "1": 2})

嵌套字典适合快速累加,但不利于按列筛选、分组和导出。当设置维度增多(基、比特、时间戳、批次)时,字典的键会迅速变得难以维护,这正是需要引入 DataFrame 的地方。

展开为统计表:DataFrame 的列设计

把嵌套字典“拍平”成一张长表,每行是一个(设置,标签)组合,列包括设置字段、标签、计数与概率。概率按设置分组计算:

import pandas as pd

rows = []
for (basis, qubit), c in counts.items():
    total = sum(c.values())
    for label, n in c.items():
        rows.append({
            "basis": basis, "qubit": qubit,
            "label": label, "count": n,
            "prob": n / total,
        })

df = pd.DataFrame(rows)
df = df.sort_values(["basis", "label"]).reset_index(drop=True)

得到的表结构如下(示例数据):

basisqubitlabelcountprob
X0010.333
X0120.667
Z0020.667
Z0110.333

这张表就是“从实验记录到统计表”的中间产物。它满足三个可复查条件:每行计数可回查到原始事件;每个设置内概率之和为 1;设置字段与标签字段分离,便于后续按任意维度分组。

结构关系一图看清

下面的图示把“原始事件 → 分组计数 → 统计表 → 概率估计”的数据流,以及设置与标签两个维度的正交关系放在同一张图里,便于对照代码理解字段来源。

原始事件记录 每条 = 一次测量 basis=Z, label=0 basis=Z, label=1 basis=X, label=1 按设置分组 嵌套字典计数 键 = (设置, 标签) ("Z",0): {"0":2,"1":1} ("X",0): {"0":1,"1":2} 值 = 计数 拍平 Pandas DataFrame 每行 = 一个(设置,标签)组合 basis | qubit | label | count | prob Z | 0 | 0 | 2 | 0.667 X | 0 | 1 | 2 | 0.667 两个正交维度 设置维度决定“怎么测”,标签维度决定“测到什么” 测量设置 basis: Z / X qubit: 0 / 1 batch / 时间戳 结果标签 单比特: 0 / 1 多比特: 00…11 离散、可枚举 概率估计与校验 p(label | 设置) = count / 该设置总计数 Z 基:2/3 ≈ 0.667,1/3 ≈ 0.333,和为 1 X 基:1/3 ≈ 0.333,2/3 ≈ 0.667,和为 1 校验:分组归一化,不可跨设置混算

从统计表到可复用结构

有了长表,常见操作都变成一行 Pandas 表达式。例如按设置取子表、按概率排序、把多比特标签拆成两列:

z_df = df[df["basis"] == "Z"]
top = df.sort_values("prob", ascending=False).head(2)

# 多比特标签拆分(示例:label 为 "01")
df["bit0"] = df["label"].str[0]
df["bit1"] = df["label"].str[1]

若实验规模较大,更推荐“宽表 + 长表”双视图:宽表以设置为行、标签为列存放计数,便于人眼比对;长表用于分组计算与绘图。两者可由 pivot_table 与 melt 互相转换,但概率列始终在长表上按设置分组计算,避免宽表转置时误用全局分母。

字段命名建议固定为 basis、qubit、label、count、prob,并在数据字典中写明每个字段的取值域与单位。设置字段一旦增加(如新增 batch),只需在分组键中加入该列,统计逻辑无需重写。
常见错误:把不同测量设置的结果合并后统一归一化,或把 label 当数值参与平均。结果标签是类别量,只能计数与比较频率,不能直接求算术平均;需要数值化时应先明确编码规则(如 0/1 映射)并单独记录。

至此,实验记录被整理成一张字段清晰、分组明确、可校验的统计表。它既是后续频率分布图、误差棒与假设检验的输入,也是复现实验时核对“设置—标签—计数”链条是否完整的依据。