如何处理加权数据
在某些社会科学数据集中,你会发现一个名为“权重”的列。此列提供调查或设计权重,用于调整每个个体对分析的贡献。如果你的数据集不包含这样的变量,你可以放心地忽略此功能。但是,了解权重的工作原理将帮助你更深入地理解社会序列分析。
什么是权重?
在许多社会科学数据集中,每一行并不代表相同的总体比例。例如:
- 在调查数据集中,一些受访者可能会携带更高的抽样权重,以纠正不均等的选择概率。
示例: 在 pairfam-family 数据集 中,提供了变量weight40,以便 40 岁时的轨迹能够代表德国人口。如果你忽略它,某些社会群体的代表性可能会过高或过低。
例如:
id = 111000
weight40 = 0.343964278697968这并不意味着这个人是“三分之一的人”。相反,Pairfam 采用了复杂的抽样设计(分层、不等的选择概率),并且随着时间的推移,也存在流失(退出)。
为了使样本能够代表 40 岁时的德国人口,调查团队提供了一个校准权重。该值告诉我们在计算人口估计值时,这个人应该占多大比例。
因此,如果 weight40 < 1(例如 0.34),则该受访者来自原始样本中代表性过高的群体,因此必须降低其影响力。
如果 weight40 > 1(例如 1.7),则该受访者来自代表性不足的群体,因此必须提高其影响力。
在实践中,使用权重意味着描述性统计量和序列分布反映人口结构,而不仅仅是原始样本。
在行政数据集中,一些个体可能代表许多其他个体(例如,当行按组聚合时)。
示例:如果每一行对应一个市镇的失业轨迹,并且数据集还包含该市镇的规模,则使用该规模作为权重可确保大市镇的权重高于小市镇。
在模拟数据集中,您可能希望优先考虑某些情况。
示例:假设您模拟了 10 条“稀有”职业道路,但希望它们对 100 条“常见”职业道路的贡献达到同等程度。您可以为每条稀有职业道路赋予 weight = 10,以便它们在计算描述性统计或替代成本时具有相同的影响力。
总的来说,权重只是附加在每个序列上的一个非负数,它告诉您该序列在描述性统计和成本估算中应该占多少比重。
默认情况下,Sequenzo 假设所有序列的权重相同(SequenceData() 中的 weight = 1)。
为什么要使用权重?
权重可以让您的结果反映真实的总体结构,而不仅仅是原始样本。 例如:
- 不使用权重:一个较小的过采样子群体可能会主导用于计算替代成本的转换率。
- 使用权重:替代成本反映了预期的总体频率。
如果您正在分析调查数据或已分层、聚类或聚合的数据,这一点尤为重要。
如何使用 Sequenzo 提供权重
定义数据集时,请传递一个权重数组:
from sequenzo import SequenceData
seq = SequenceData(
data=df,
time=list(df.columns)[1:], # 序列列
states=["A", "B", "C"],
id_col="ID",
weights=df["survey_weight"].values # 每个序列一个权重
)如果没有指定权重,Sequenzo 会自动将其设置为 1。
权重如何改变序列分析工作流程
大部分工作流程保持不变:您仍然可以像往常一样计算距离、进行聚类和可视化。 但权重在以下几个关键位置至关重要:
SequenceData()中的描述性统计和交叉表
sequence_data.get_xtabs(other, weighted=True)在计数共现时使用权重。- 加权计数会影响转移概率和派生的摘要。
SequenceData()中的唯一性统计
sequence_data.uniqueness_stats(weighted=True)报告考虑权重后的唯一性率。- 例如:如果一个稀有序列的权重非常大,它会按比例贡献。
- 替换成本矩阵 (
sm)
- 如果您选择
sm="TRATE"(基于转移率),Sequenzo 将使用加权转移率。 - 这意味着替换成本反映的是群体加权转移,而非原始频率。
- 如果
sm="CONSTANT",权重将不起作用。
- 插入/删除成本 (
indel)
- 当
indel="auto"时,其值可能来自替换矩阵。 - 如果替换矩阵是使用权重构建的,则 indel 成本间接依赖于权重。
- 可视化
大多数 Sequenzo 图现在接受可选的权重参数(默认值:“auto”)。 当设置为“auto”时,该函数使用sequence_data.weights(如果可用);否则,它会回落到相同的权重。
- 总结频率或平均值的图(状态分布、模态状态、最频繁序列、平均时间、转移矩阵、相对频率图)计算加权计数并除以总权重。
- 纯“行渲染”图(序列索引)不会按权重复制行;相反,我们提供重量感知排序和标签。
- 比例的误差线使用有效样本量
来近似 。
这使得在存在权重时图像能够忠实于群体结构,而在不存在权重时保持向后兼容的行为。
- 下游分析
- 成对距离(OM、HAM、DHD 等)并非直接按序列加权。
- 相反,权重会塑造成本矩阵,进而间接影响距离。
对于 K-Medoids / PAM / PAMonce,权重在目标函数中明确使用:在计算总成本时,每个点到其中心点的距离都会乘以其权重。
换句话说,使用权重时,所选的中心点和最终的聚类分配可能与等权重的情况不同;如果权重较大,一些序列甚至可能占据主导地位。
与 K-Medoids 不同,层次聚类的序列权重并不应用于链接构建本身,而仅用于评估聚类质量。详情如下。
层次聚类中的加权数据
为什么权重应用于聚类质量指标 (CQI) 而不是链接函数
在 Sequenzo 中,聚类质量指标(例如 ASWw、R²、HG)和 K-Medoids / PAM 变体 支持权重,但分层链接计算(位于 Cluster() 函数的 method=ward 参数中)不支持。这是有意为之,并且符合方法论和实践方面的考虑。
1. 质量指标天生可加权。
大多数评估统计数据被定义为案例的平均值(例如平均轮廓宽度、解释方差)。将其扩展为加权版本很简单:每个案例按比例贡献其权重。这确保了对聚类解决方案的评估能够正确反映样本设计或总体重要性。
2.层次链接更为复杂。
许多流行的链接规则,例如 Ward 规则(常用于序列分析)、质心规则或中值规则,都源于仅在欧几里得特征空间中成立的几何解释。
当应用于非欧几里得相异度(例如最佳匹配或其他序列相异度距离)时,这些公式本身就只是近似值。在顶层添加权重并没有明确或被广泛接受的定义,并且可能会产生不稳定或误导性的树状图。
3. 加权链接的适用性有限。
在常见的链接方法中,只有平均链接 (UPGMA) 可以安全地扩展以包含权重:簇之间的距离定义为所有成对相异度的加权平均值。
对于单链接或完全链接,权重无关紧要(它们始终使用最小/最大权重)。对于 Ward 和基于质心的方法,有效的加权版本需要访问欧几里得坐标,而这对于一般序列距离是不可用的。
4. Sequenzo 中的设计选择。
由于这些限制,Sequenzo 不尝试提供加权链接。相反,需要加权聚类的用户应该依赖:
- 使用加权质量指标评估分层划分,或
- 加权 K-Medoids / PAM / PAMonce 实现。
这种方法确保在理论合理的情况下尊重权重,同时避免使用那些给人以虚假严谨感的方法。
K-Medoids 及其变体中的加权数据
Sequenzo 实现了 K-Medoids 聚类以及两种常见变体:PAM(围绕 Medoids 划分)和 PAMonce。这三种方法都通过最小化每个序列与其指定 Medoid(聚类中最具代表性的序列)之间的总差异来对序列进行分组。与生成完整树的层次聚类不同,K-Medoids 直接返回数据的平面分区。
算法差异
- KMedoids(标准)
此版本遵循经典算法:反复将候选中心点与非中心点交换,直到无法进一步改进。 它保证在足够多的遍历次数下实现最优分割,但在大型数据集上计算量较大。
- PAM(围绕中心点进行分割)
PAM 也寻求最优中心点,但通常在每次迭代中探索更广泛的交换和改进范围。它往往比 KMedoids 提供更稳定的结果,但计算成本更高。
- PAMonce
这是一个更快的近似变体。它不需要反复更新中心点,而是仅从初始选择执行一次细化。 PAMonce 在大型数据集上速度更快,并且通常能产生接近完整 PAM 算法的结果,尽管有时会牺牲聚类质量。
权重的作用
这三种方法都接受 weights 参数。默认情况下,每个序列具有相同的权重(weights = 1)。 当您提供权重向量时,聚类目标会发生变化:
- 每个序列与其指定中心点的距离乘以其权重。
- 更大的权重会使序列在选择中心点和形成聚类时更具影响力。
- 在极端情况下,单个具有非常大权重的序列可能会主导聚类的形成。
这使得加权 K-Medoids 在处理调查数据、分层样本或聚合序列(其中某些单元比其他单元代表更大的总体)时特别有用。
实用指导
- 如果您想要具有总体代表性的聚类,请务必传递调查或设计权重。
- 如果您只关心样本结构,则可以忽略权重并平等对待所有序列。
- 对于大型数据集,请先尝试PAMonce以提高速度,如果需要更精确的分区,请切换到PAM。
- 使用
npass参数控制尝试随机重启的次数。更多遍历次数会增加稳定性,但会缩短运行时间。
K-Medoids / PAM / PAMonce:加权与非加权(玩具示例)
下面是一个最小的、独立的示例,无需先计算序列距离即可运行(我们使用了一个小型的手工制作的相异度矩阵)。它演示了三件事:
- 如何调用 KMedoids、PAM 和 PAMonce
- 如何传递权重向量
- 权重如何改变所选的 medoids 和分配
设置
import numpy as np
from sequenzo.clustering.KMedoids import KMedoids
# 对于可重复的随机初始化
np.random.seed(42)具有两个自然群的微小距离矩阵
我们构建一个 8×8 的距离矩阵,其中包含两个紧密组:
- 组 A:索引 0-3
- 组 B:索引 4-7
并且我们进行了温和的跨组分离,这样,在没有权重的情况下,两个组都同样可信。
# 8 个对象,2 个大小为 4 的自然组
n = 8
D = np.zeros((n, n), dtype=float)
# 群体内距离较小
for i in range(4):
for j in range(4):
if i != j:
D[i, j] = 0.3 # 组 A (0-3)
for i in range(4, 8):
for j in range(4, 8):
if i != j:
D[i, j] = 0.3 # 组 B (4-7)
# 跨组距离较大,但并不大
for i in range(4):
for j in range(4, 8):
D[i, j] = D[j, i] = 1.0
# 理智:零对角线
np.fill_diagonal(D, 0.0)
print("Distance matrix D:\n", np.round(D, 2))案例 1:PAMonce(快速),无加权
k = 2
labels_unweighted = KMedoids(
diss=D,
k=k,
method="PAMonce", # 快速单遍细化
npass=10, # 10 次随机重启;增加以获得额外的稳定性
weights=None # None -> all ones (权重相等)
)
print("\n[PAMonce | unweighted] cluster labels:", labels_unweighted)案例 2:PAMonce,加权
现在,我们赋予一个对象非常大的权重,使其在总体中占据更大的比例。这通常会改变哪些中心点是最优的,并可能颠覆一些分配。
# 所有人的重量都为 1,除了对象 #6(索引 6)非常重
w = np.ones(n, dtype=float)
w[6] = 50.0
labels_weighted = KMedoids(
diss=D,
k=k,
method="PAMonce",
npass=10,
weights=w
)
print("[PAMonce | weighted] cluster labels:", labels_weighted)
print("Weights:", w)预期结果:
- 未加权:两个平衡聚类 (0-3) vs. (4-7)。
- 加权:算法优先选择使重物 (#6) 靠近其中心点的解;所选的中心点和最终划分可能会相应移动(例如,4-7 内的中心点可能会移近 #6,或者 #6 可能会将边界情况“拉”到划分的另一端)。
案例 3:完整 PAM(更彻底),加权
PAM 比 PAMonce 探索了更多的交换/改进。它速度较慢,但可以生成更高质量的分区。如果有时间,可以比较一下:
labels_pam_weighted = KMedoids(
diss=D,
k=k,
method="PAM", # 比 PAMonce 更彻底
npass=5, # 几次重启(对于大型数据集增加)
weights=w
)
print("[PAM | weighted] cluster labels:", labels_pam_weighted)案例 4:经典 KMedoids,加权
这是教科书的变体;它迭代交换直到无法改进(给定当前运行),通常根据实施细节在成本/速度配置文件中在“PAMonce”和“PAM”之间进行。
labels_kmedoids_weighted = KMedoids(
diss=D,
k=k,
method="KMedoids",
npass=5,
weights=w
)
print("[KMedoids | weighted] cluster labels:", labels_kmedoids_weighted)解释结果
- 这三种变体都接受权重并在目标函数中使用它们(每个对象与其中心点的差异度乘以其权重)。
- 如果设置了一个或几个非常大的权重,这些对象将强烈影响中心点的选择和最终的划分。
- 对于大型数据集,建议先使用PAMonce来提高速度。如有需要,请在子集上使用PAM进行验证,或减少迭代次数。
- 如果下游分析需要具有总体代表性的聚类(例如,调查数据),请在此处传递调查/设计权重。如果只关心样本的内部结构,则可以放心地省略权重。
提示:为了可重复性,设置
np.random.seed(...)并使用非零npass(多次重启)以降低对初始化的敏感性。
使用初始 medoids(可选)
如果已经有了很好的候选 medoid(例如,来自领域知识或热启动),可以通过initialclust传递它们:
# 假设我们想从索引 [1, 6] 处的 medoids 开始
labels_from_init = KMedoids(
diss=D,
k=2,
method="PAMonce",
initialclust=[1, 6], # 中心点索引(从 0 开始)
npass=0, # 当你信任 init 时跳过随机重启
weights=w
)
print("[PAMonce | weighted | init=[1,6]] labels:", labels_from_init)当 initialclust 以成员向量(每个对象一个标签)或分层链接矩阵的形式提供时,Sequenzo 会在内部将其转换为起始中心点(详情请参阅文档字符串)。当想将在子样本中找到的解决方案“提升”到完整数据集时,这非常方便。
作者: 梁彧祺
翻译:曲思竹