三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

用Python实战马氏性检验:从数据清洗到卡方检验的完整流程(附代码避坑)

用Python实战马氏性检验:从数据清洗到卡方检验的完整流程(附代码避坑)

用Python实战马氏性检验:从数据清洗到卡方检验的完整流程(附代码避坑)

在金融交易行为分析或工业设备预测性维护中,我们常遇到这样的场景:当系统当前处于状态A时,下一个时刻转移到状态B的概率是否仅取决于当前状态?这就是马尔可夫性(无后效性)的核心命题。本文将用Python带您完整实现从原始离散状态序列到马氏性验证的全流程,重点解决实际工程中的三个关键问题:零频次处理、自由度修正和显著性水平选择。

1. 环境准备与数据预处理

1.1 工具库选择逻辑

不同于理论推导,工程实现需要平衡计算效率与代码可读性。我们采用以下工具组合:

import numpy as np # 矩阵运算核心 import pandas as pd # 数据清洗利器 from scipy.stats import chi2 # 卡方检验实现

1.2 状态序列标准化

原始数据往往存在格式混乱问题,需统一转换为数值型状态序列。这里演示如何处理包含文本标签的混合数据:

def normalize_states(raw_series): state_mapping = {v:k for k,v in enumerate(raw_series.unique())} return raw_series.map(state_mapping).astype(int) # 示例:转换文本状态到数字编码 raw_data = pd.Series(['正常', '故障', '正常', '待机', '故障']) normalized = normalize_states(raw_data) print(normalized) # 输出: 0 1 0 2 1

注意:当状态种类超过20个时,建议先进行状态聚类,避免后续卡方检验自由度爆炸。

2. 构建转移频数矩阵

2.1 高效计数算法

传统循环写法在长序列下性能堪忧,我们利用numpy的向量化运算实现O(n)复杂度:

def build_transition_matrix(states, n_states): matrix = np.zeros((n_states, n_states), dtype=int) for (i, j) in zip(states[:-1], states[1:]): matrix[i, j] += 1 return matrix # 使用示例 states = np.array([0, 1, 0, 2, 1, 0]) trans_mat = build_transition_matrix(states, n_states=3) print(trans_mat)

2.2 零频次处理方案

当某些转移从未发生时,直接计算概率会导致除零错误。工程中常用平滑处理方法:

方法公式适用场景
拉普拉斯平滑(f_ij + α)/(N + αm)小样本数据
背景概率替代max(f_ij, ε)实时流数据
状态合并合并低频状态高维状态空间
def safe_transition_prob(matrix, method='laplace', alpha=1e-3): if method == 'laplace': return (matrix + alpha) / (matrix.sum(axis=1)[:, None] + alpha * matrix.shape[0]) elif method == 'floor': return matrix / np.maximum(matrix.sum(axis=1)[:, None], alpha)

3. 卡方检验实现细节

3.1 自由度计算陷阱

许多教程忽略了一个关键点:当原始序列存在吸收态(absorbing state)时,实际自由度需要调整。正确的计算方法:

def effective_degrees_of_freedom(trans_mat): n = trans_mat.shape[0] # 减去吸收态数量 absorbing_states = np.where(np.diag(trans_mat) == trans_mat.sum(axis=1))[0] return (n - len(absorbing_states) - 1) ** 2

3.2 完整检验流程

将理论公式转化为可执行的代码单元:

def markov_test(trans_mat, alpha=0.05): n = trans_mat.shape[0] row_sums = trans_mat.sum(axis=1) col_sums = trans_mat.sum(axis=0) total = trans_mat.sum() chi_sq = 0 for i in range(n): for j in range(n): if trans_mat[i,j] > 0: expected = row_sums[i] * col_sums[j] / total chi_sq += trans_mat[i,j] * np.log(trans_mat[i,j] / expected) chi_sq *= 2 df = effective_degrees_of_freedom(trans_mat) critical = chi2.ppf(1 - alpha, df) return chi_sq > critical, chi_sq, critical

4. 工程实践中的典型问题

4.1 样本量不足的应对策略

当警告"预期频数小于5的单元格超过20%"时,可考虑:

  1. 时间聚合:将1分钟粒度的状态序列转为5分钟粒度
  2. 状态归并:合并语义相似的状态类别
  3. Bootstrap重采样:生成合成数据保持分布特性

4.2 多序列聚合检验

对于跨多个设备的独立状态序列,可采用分层检验方法:

def pooled_markov_test(trans_mats): pooled_mat = sum(trans_mats) # 矩阵元素求和 return markov_test(pooled_mat)

5. 可视化诊断与结果解读

5.1 转移热力图分析

使用seaborn快速生成转移概率可视化:

import seaborn as sns import matplotlib.pyplot as plt def plot_transition_heatmap(trans_mat): prob_mat = trans_mat / trans_mat.sum(axis=1, keepdims=True) sns.heatmap(prob_mat, annot=True, fmt=".2f", cmap="YlGnBu") plt.xlabel("Next State") plt.ylabel("Current State")

5.2 检验结果报告模板

给出专业的技术结论表述框架:

示例报告:

经卡方检验(χ²=15.72 > χ²_crit=9.49,df=4), 在0.05显著性水平下拒绝原假设,该序列具有显著马尔可夫性。 转移概率矩阵显示从"故障"状态返回"正常"的概率仅0.2, 建议增加该状态的监测频率。

在电商用户行为分析中,我们发现状态转移存在明显的时间段差异——午间浏览会话更符合马尔可夫性,而夜间购物车操作则呈现多步依赖特征。这种洞察帮助我们优化了不同时段的推荐策略。

← 返回列表