跳转到内容

get_distance_matrix():计算距离矩阵

get_distance_matrix() 是 Sequenzo 里进行序列对比的核心函数。它把一组离散序列(比如职业路径、家庭轨迹、健康状态)两两对比,产出一个数值矩阵,告诉你每条序列与其他序列究竟有多不一样。这个不一样的数叫不相似度或者距离(dissimilarity),用来计算这个距离值的算法叫距离算法

需要强调一下:get_distance_matrix() 的输出始终是一个n × n的矩阵,n 是你数据里的序列条数。这个距离矩阵是很多下游分析任务的起点,例如聚类分析。在文献中,聚类得到的组别也常被称为类型(typologies)。此外,距离矩阵也常用于可视化(visualization)以及在序列数据上做回归(regression)。

如果你是刚接触序列分析的小白,想系统了解究竟选择哪个算法、这些参数又是什么意思,建议先看这份《距离算法指南》。如果你能先认真读完这份指南,然后再来阅读本文档的话,就会省时又省力。

已经熟悉了?那直接往下看用法就行。

算法速查(method cheatsheet):什么时候用哪个?

  • OM(Optimal Matching):通用的基于状态的编辑距离算法(即edit distance 类算法)。支持自定义 sm(替换成本)与 indel(插入/删除成本)。它之所以被称为“编辑距离”,是因为它通过替换、插入和删除等最小编辑操作来比较序列。

  • OMspell:与 OM 类似,但在片段/连续段上操作;当持续时长重要的时候更合适。

  • HAM(Hamming-like positional distance):简单的按位置替换;要求序列等长。

  • DHD(Dynamic Hamming with time-varying costs):类似 HAM,但成本可以随着位置变化(通过转移率 transition rates 实现)。

  • LCP/RLCP:基于公共前缀的距离;用于强调早期路径的相似度(LCP)或者它的反方向(RLCP)。

函数用法

可用的算法有多种(详见指南)。由于 get_distance_matrix() 需要同时支持所有的算法,参数看起来会比较多。但在实际使用中,每一种距离算法只需要少量的关键参数,刚刚我们括号里提到的指南对这些参数都有清晰的说明。

简单来说,要先定好要用哪种算法,然后设定 method,再配上与这个算法相关的少数几个参数就行。比如常见的最优匹配算法(Optimal Matching,method="OM"),主要就是决定替换与插入/删除成本(smindel),以及可选的归一化方案(norm)。

提示:下面的代码块列举了所有支持的参数以及可供参考的典型值;在我们实际使用中通常不需要全部设置。我们在这里先给大家展示所有参数,让大家有个印象,之后会对每种算法所需要的参数挨个讲解。

python
om = get_distance_matrix(
    seqdata=sequence_data,   # SequenceData 对象
    method="OM",             # "OM", "OMspell", "HAM", "DHD", "LCP", 或 "RLCP"
    sm="TRATE",              # 替换成本的规格或矩阵(见下文)
    indel="auto",            # number | vector | "auto"(仅用于 OM/OMspell)
    norm="auto",             # "none" | "auto" | "maxlength" | "gmean" | "maxdist" | "YujianBo"
    full_matrix=True,        # 返回完整 n×n 矩阵
    refseq=None,             # 或传入两个索引集合:[idx_list_A, idx_list_B]
    tpow=1.0,                # 仅用于 OMspell:片段长度指数(spell-length exponent)
    expcost=0.5,             # 仅用于 OMspell:片段转换成本(spell transform cost)
    weighted=True,           # 在从数据构建 sm(如 "TRATE")时考虑序列权重
    check_max_size=True      # 针对极多序列的安全检查防止超过pandas/numpy可支持的最大索引
)

不同算法的参数设置

首先,我们来看各个算法的默认设置。把默认参数搞明白,后面就可以更好地理解各个算法的原理,也好选择适合的算法和调整相应的参数。一般来说,需要你按照自己的研究问题或理论假设去选择算法、修改参数,但如果你暂时还没有明确的想法,一个稳妥的起点是 method = "OM"sm="CONSTANT"(任意状态的替换成本均为 2),indel=1(增删成本)。更多解释见指南

  • OM(Optimal Matching,通用)sm 由用户设定,indel="auto"norm="auto"

  • OMspell(片段时长敏感)sm 由用户设定,indel="auto"tpow=1.0expcost=0.5norm="auto"

  • HAM(Hamming,严格按位置):若没有设定 sm,默认使用常数替换矩阵,所有成本为 2(sm="CONSTANT", cval=2),norm="auto"。该算法要求所有序列等长;例如,你的数据中每条序列都覆盖恰好10个时间点/年份。

  • DHD(Dynamic Hamming,按位置且成本随时间变化):默认 sm="TRATE"norm="auto"。要求序列等长。

  • LCP / RLCP(Longest Common Prefix / Reversed LCP,前缀相似)norm="auto",不需要 smindel

所有算法通用的参数

参数必填数据类型说明
seqdataSequenceDataSequenzo 的状态序列对象。
methodstr"OM""OMspell""HAM""DHD""LCP""RLCP" 之一。
refseqlist两个索引列表 [A, B]。返回 A × B 的距离表。如果为 None,计算所有序列的距离。
normstr"none""auto""maxlength""gmean""maxdist""YujianBo""auto" 会按算法自动选择合理的默认值。
full_matrixbool"refseq=None" 时,"full_matrix=True"的下三角矩阵 就给你完整的 n×n矩阵,"full_matrix=False" 就给距离矩阵的下三角矩阵(用一维数组存储)。如果是 "refseq = None","full_matrix" 就会被忽略。
weightedbool当从数据构建 sm(例如 "TRATE")时,如果有权重则予以考虑。
check_max_sizebool如果序列数量过大,会超过索引范围,此时将提前停止。
optsdict用字典方式成组传参。
**kwargswith_missing 被忽略;缺失值会始终作为额外状态在函数内部处理。

基于编辑的算法:OM、OMspell

参数必传类型适用方法说明
smOMOMspell时必传str 或矩阵OM, OMspell替换成本。可用 "TRATE"(基于状态转移)、"CONSTANT"(单一常数,即 sm=2),或提供方形的矩阵(状态 × 状态)。
indel数字、向量或 "auto"OM, OMspell插入/删除成本。数字表示常数;向量指按状态设置成本(长度需等于状态数,包含缺失);"auto" 会依据 sm 推导出一致的值。
normstrOM, OMspell推荐默认:OM 为 "maxlength"OMspell 为 "YujianBo"
tpowfloatOMspell片段长度的指数权重,控制对长片段的强调程度。
expcostfloat(正数)OMspell片段的转换成本,值越大,对片段拉伸或压缩的惩罚越大。

OMspell 常见于文献。它比较的是片段(spells,指同一状态的连续片段) 而非单个位置,因此当持续时长很重要时更合适。

OMspell 参数的实用提示

参数常见取值含义实用建议
expcost(δ)0、0.1、0.5、1将片段长度扩大或缩小 1 个时间单位的成本,控制对持续时间的敏感度。0:基本忽略时长,只看状态顺序。
0.1–0.5:对时长有中等敏感度(常用)。
1:强烈关注长短差异。
tpow0.5–2基于片段时长的指数权重。1.0:线性(默认)。
小于 1:弱化长片段,强调短片段;
大于 1:放大长片段影响。
indel1–5相对替换的插入/删除成本。值越高越强调精确对齐(对齐较少);值越低允许更多的移动,更关注顺序与持续时间模式。
sm"TRATE" 或固定值替换成本策略。"TRATE" 常与 indel="auto" 联用;固定值 2 是不使用转移成本时的常见基线。

OMspell 使用指引

  • 主要看顺序(sequencing)时:将 expcost 设为接近 0。

  • 同时看顺序和持续时长(duration)时:取中等 expcost(0.1–0.5),并配合 tpow ≈ 1

  • 关注长短差异(长段 vs 短段)时:使用较高的 expcost(≈ 1),并设置 tpow > 1。例如长期失业 vs 反复短期失业。

说明

  • smTRATE 的时候,替换成本会从你的数据中学习;另一情况下,当 indelauto 的时候,会自动给出与 sm 一致的 indel 值。

  • OMspell 使用片段(spell,连续相同状态的片段)与片段时长;你可以通过 tpowexpcost 为片段长度加权。如果你关注片段持续时间的差异,按照 Studer 与 Ritschard(2016)的建议,应选择 OMspell;因为它会显式考虑状态持续多久,而不仅仅是状态的先后顺序。

基于位置度量(Position-wise measures):HAM、DHD

参数必填与否类型适用方法说明
smHAM 非必填,DHD 必填str 或矩阵或三维数组HAM, DHDHAM:若未指定 sm,将自动创建一个常数为 2 的替换成本矩阵(即任意状态替换成本均为 2,"cval=2");DHD:使用 "TRATE",或提供三维数组(随时间位置变化的成本)。
normstrHAM, DHD推荐在 norm="auto" 下使用 "maxlength"

说明

  • DHD 在 HAM 的基础上做了推广,允许成本随时间位置变化(例如,可以对早期与晚期差异赋予不同权重)。

  • 所有序列必须等长(按位置比较)。

基于前缀的算法:LCP、RLCP

参数必填类型适用方法说明
normstrLCP, RLCPnorm="auto" 下,推荐默认值为 "gmean"

说明

  • 这里不需要替换成本(substitution costs)或插入/删除成本(indel costs)。

  • LCP 强调原始顺序中的公共前缀;RLCP 在序列反转后强调公共前缀(即原序列中的公共后缀)。

  • 当你关注早期路径相似度(或通过 RLCP 关注晚期路径相似度)时很有用。

这个函数做了什么

  1. 校验输入与支持的方法

    支持的方法:OM、OMspell、HAM、DHD、LCP、RLCP。

  2. 构建替换成本与插入/删除成本

    • 当传入 sm="TRATE" 时,成本来自你的数据中的转移率(在 DHD 中会随时间位置变化)。

    • 当在 HAM 中传入 sm="CONSTANT" 时,使用简单的常数2,因为sm现在不支持直接传常数作为替换成本。

    • indel="auto"sm 已知时,会推导出与 sm 一致的 indel

    • 你也可以提供自定义的 sm 替换成本矩阵,一个方形矩阵(k × k),其中 k 为状态数,sm[i][j] 表示从状态 i 到状态 j 的替换成本(通常设为对称,但并非必须)。对于 DHD,你可以提供按时间位置的三维数组 T × k × k(time × state × state),允许成本随时间变化。

  3. 按照你研究设计的需要(归一化距离)

    • norm="auto" 时会自动选择:

      • OM、HAM、DHD → "maxlength"

      • LCP/RLCP → "gmean"

      • OMspell → "YujianBo"

    • 你也可以改为列表中的其他可用归一化方式(不支持的除外)。

  4. 内部去重相同序列

    • 函数会把数据压缩为非重复序列,从而加速 C++ 距离计算。

    • 计算完非重复序列之间的距离后,再展开回你要求的输出形状。

  5. 使用编译后端计算距离

    • 计算核心由 C++ 完成以提升速度。

    • refseq=Nonefull_matrix=True 返回完整 n×n 矩阵;full_matrix=False 返回距离矩阵的下三角矩阵。

    • refseq=[idxs_A, idxs_B]:返回一个 |A|×|B| 的 DataFrame,行/列标签来自你的原始 ID。

  6. 安全与边界检查

    • 若存在空序列,会给出警告(或算法无法处理空序列的报错)。

    • full_matrix=Falserefseq is not None,仍会返回完整矩阵(会输出针对此情况的信息)。

    • 若序列数量超过安全上限,函数会抛出清晰的错误,除非设置 check_max_size=False

示例

1) 基于转移率成本的 OM(通用的默认设置)

python
om = get_distance_matrix(
seqdata=sequence_data,
method="OM",
sm="TRATE",     # 从观测到的转移构建成本
indel="auto",
norm="auto",    # OM 时用 "maxlength"
full_matrix=True
)

2) 片段敏感的 OM(时长重要)

python
omspell = get_distance_matrix(
seqdata=sequence_data,
method="OMspell",
sm="TRATE",
indel="auto",
tpow=1.0,
expcost=0.5,
norm="auto"     # OMspell 时用 "YujianBo"
)

3) HAM(等长序列)

python
ham = get_distance_matrix(
    seqdata=sequence_data_equal_length,
    method="HAM",
    sm="CONSTANT",   # 常数替换成本
    norm="auto"      #  HAM 时用 "maxlength"
)

4) DHD(等长,成本随位置而变化)

python
dhd = get_distance_matrix(
    seqdata=sequence_data_equal_length,
    method="DHD",
    sm="TRATE",     # 内部构建按位置的三维成本数组
    norm="auto"
)

5) LCP 与 RLCP(强调早期路径或它的反方向)

python
lcp = get_distance_matrix(seqdata=sequence_data, method="LCP",  norm="gmean")
rlcp = get_distance_matrix(seqdata=sequence_data, method="RLCP", norm="gmean")

6) 两组之间的距离(A 与 B)

python
idxs_A = list(range(0, 100))     # 前 100 个实体
idxs_B = [10, 50, 250, 400]      # 参与比较的实体集合
ab = get_distance_matrix(
    seqdata=sequence_data,
    method="OM",
    sm="TRATE",
    refseq=[idxs_A, idxs_B],     # 返回 |A|×|B| 的 DataFrame
    full_matrix=True             # 此处被忽略;总是返回完整表
)

7) 仅计算下三角矩阵以节省内存

python
reduced = get_distance_matrix(
seqdata=sequence_data,
method="OM",
sm="TRATE",
full_matrix=False            # 返回距离矩阵的下三角矩阵(一维数组)
)

小贴士与常见坑

  • 对于 HAM/DHD,请确保序列长度一致;否则会触发明确的报错。

  • 若为 indel 提供了一个向量,其长度必须与 seqdata.states 中的状态数量一致(按字母表顺序,包含缺失状态)。

  • with_missing 参数已移除;缺失值会被一致地默认处理。

返回值

返回一个距离的 Pandas DataFrame。 形状如下:

  • refseq=Nonefull_matrix=Truen×n

  • refseq=Nonefull_matrix=False(u×u)/2(u 为非重复序列数量);

  • refseq=[idxs_A, idxs_B]|A|×|B|

  • 行/列标签取自 seqdata.ids

代码:李欣怡

文档:梁彧祺

编辑:梁彧祺

翻译:明煜坤

参考文献

Studer, Matthias, and Gilbert Ritschard. "What matters in differences between life trajectories: A comparative review of sequence dissimilarity measures." Journal of the Royal Statistical Society Series A: Statistics in Society 179, no. 2 (2016): 481-511.

Released under the BSD-3-Clause License.