dimension-definer-pro
维度定义处理 Skill Pro v2.1.7
通用型维度定义处理工具,采用5步标准化处理流程,支持从多种输入格式自动提取、匹配、生成标准化的维度定义表。
v2.1.6 重要改进(2026-04-01)
1. 数据逻辑定义输出规则修复
修复问题:
- 数据逻辑定义使用 definition 中的变量前缀查找值标签,导致找不到值标签时输出
D4a=code1格式 - 多 code 值(如
D3a=3/4)只处理第一个 code,未输出完整的值标签列表
修复方案:
- 使用匹配到的 var_name 查找值标签:不是使用 definition 中的变量前缀(如 D4a),而是使用匹配到的实际变量名(如 D4_L2_城市级别)
- 建立简写变量名映射:值标签表中的变量名是完整形式(如 D2_您的性别是),建立简写到完整的映射关系
- 支持多 code 值连接:
/分隔的 code 用 "或" 连接,&或,分隔的 code 用 "且" 连接
修复效果:
| 场景 | 修复前 | 修复后 |
|---|---|---|
| 单code,有值标签 | D2=code1 |
D2=男性 |
| 多code,有值标签 | D3a=code3 |
D3a=25-29岁或D3a=30-34岁 |
| 无值标签 | D4a=code1 |
D4_L2_城市级别=code1 |
输出规则:
- 使用匹配到的
var_name(不是 definition 中的变量前缀)查找值标签 - 如果能找到值标签,输出:
var_name=值标签 - 如果找不到值标签,输出:
var_name=codeX - 多 code 值用 "或" 或 "且" 连接
2. 需求信息列 dim_type 前向填充修复(2026-04-01)
修复问题:3行结构中,需求信息列的 dim_type 只在特定列有值,后续列的 dim_type 为空,导致所有维度值都使用第一个 dim_type(如"城市级别")
根因分析:
- Banner1 的 3行结构:行3 (Total行) 有
Col2:城市级别,Col7:性别,Col9:年龄 - 原代码只在循环外部提取一次
current_dim_type,没有根据列位置动态获取 - 结果:Col7 和 Col9 的维度值(男性、女性、<25等)都使用了错误的 dim_type "城市级别"
修复方案:
- 先读取所有列的 dim_type:遍历 dim_type_row 的所有列,存储到
dim_type_map - 前向填充:如果当前列没有 dim_type,继承左边最近的 dim_type
- 循环内部动态获取:根据当前列位置从
dim_type_map获取 dim_type
修复效果:
| 行 | 修复前 | 修复后 |
|---|---|---|
| 行7 | Banner1-人口学-城市级别-男性 |
Banner1-人口学-性别-男性 |
| 行9 | Banner1-人口学-城市级别-<25 |
Banner1-人口学-年龄-<25 |
| 行13 | Banner1-人口学-城市级别-5万元以下 |
Banner1-人口学-家庭年收入水平1-5万元以下 |
输出规则:
- 需求信息格式:
Banner名称-维度类型-维度值 - 3行结构:dim_type 从 Total 行提取,支持前向填充
- 2行结构:dim_type 为空,使用 banner 名称
3. 智能匹配策略(v2.1.7 新增,2026-04-01)
目标:提升变量名称匹配率,通过需求信息和需求定义进行两级匹配
匹配策略:
-
第一级:通过需求定义缩小范围
- 从 Definition 提取变量前缀(如
Q20_A) - 处理多级题号(
D8-1→D8_1) - 处理双下划线格式(
Q20_A→Q20__SQ1) - 子题号映射(
A→SQ1,B→SQ2)
- 从 Definition 提取变量前缀(如
-
第二级:通过需求信息关键词筛选
- 提取平台关键词(携程、去哪儿、飞猪等)
- 提取业务/分类关键词(酒店、机票、在线智能等)
- 计算匹配得分,选择最佳候选
匹配效果:
| 版本 | 自动匹配率 | 提升 |
|---|---|---|
| v2.1.6 (修复前) | 61.0% | - |
| v2.1.7 (智能匹配) | 91.8% | +30.8% |
匹配类型分布:
| 匹配类型 | 数量 | 占比 |
|---|---|---|
| 自动匹配(智能) | 87 | 44.6% |
| 自动匹配(派生) | 48 | 24.6% |
| 自动匹配 | 41 | 21.0% |
| 自动匹配(前缀) | 3 | 1.5% |
| 自动匹配总计 | 179 | 91.8% |
| 需人工审核 | 16 | 8.2% |
示例:
Q20_A=1+携程-酒店→Q20__SQ1_1(自动匹配(智能))D8_A=2+携程-电话→D8_SQ1_2(自动匹配(智能))D8-1_A=1+携程-在线智能→D8_1_SQ1_1(自动匹配(智能))
4. 城市级别特殊匹配与数据逻辑定义修复(v2.1.7 新增,2026-04-01)
修复问题1:城市级别匹配
- 城市级别维度(如"一线城市")应该匹配到 H1 变量,而不是 D4_L2_城市级别
- 四五城市等特殊组合需要正确匹配到多个值标签
修复方案:
- 优先在值标签表中匹配 dim_value:查找包含"一线城市"等标签的变量
- 处理组合城市:如"四五城市"匹配到"四线城市"和"五线城市"
- 变量名映射:值标签表中的变量名可能是完整形式(如"H1_城市级别映射关系"),需要映射到简写形式(H1)
修复效果:
| 需求 | 修复前 | 修复后 |
|---|---|---|
| Banner1-城市级别-一线城市 | D4_L2_城市级别=code1 | H1=一线城市 |
| Banner1-城市级别-四五城市 | D4_L2_城市级别=code5或code6 | H1=四线城市或H1=五线城市 |
修复问题2:数据逻辑定义模糊匹配
- 值标签表中的变量名是完整形式(如"Q4 _SQ1_请根据您的体验...")
- 代码中使用的是简写形式(如"Q4__SQ1")
- 导致无法正确查找值标签,输出 codeX 而不是中文标签
修复方案:
- 模糊匹配:在
_get_value_label方法中,查找包含简写变量名的完整变量名 - 格式转换:处理双下划线格式(
Q4__SQ1→Q4 _SQ1)
修复效果:
| 变量 | 修复前 | 修复后 |
|---|---|---|
| Q4__SQ1 | Q4__SQ1=code4 | Q4__SQ1=比较满意 |
修复问题3:多选题默认值标签
- 多选题变量(如 D6_A1, D6_A2)在值标签表中只有变量名行,没有 code 行
- 这些变量应该使用默认值标签 {0: '否', 1: '是'}
修复方案:
- 自动识别多选题变量:包含 _A1, _A2 等子题号的变量
- 添加默认值标签:为没有值标签的多选题变量添加 {0: '否', 1: '是'}
修复效果:
- 值标签表从 1092 条增加到 2290 条
- D6_1, D6_2 等变量现在有完整的值标签
5. 值标签解析逻辑修复(v2.1.7 新增,2026-04-01)
修复问题:多选题变量(如 D5_A1, D6_A1)的值标签被错误地归到前一个变量
根因分析:
- 多选题变量在值标签表中的格式:
- 变量名行:
│D5_A1_最近一年...:国内旅行/出行│否│(没有 code) - code 行:
│(空)│1.00│是│(first_part 为空)
- 变量名行:
- 原代码只在有 code 的行更新
current_var,导致 code 行的值标签被归到前一个变量
修复方案:
- 处理变量名行:在没有 code 但有标签的行,更新
current_var但不添加值标签 - 正确处理多选题格式:识别
│变量名│标签│格式的行
修复效果:
- D10 的值标签正确解析:code 1-6 对应正确的标签
- 多选题变量(D5_A1, D6_A1 等)的值标签正确归到各自变量
6. 多选题数据逻辑定义修复(v2.1.7 新增,2026-04-01)
修复问题1:D10 数据逻辑定义输出错误
- 错误输出:
D10=是或D10=听说过,但没有使用过 - 正确输出:
D10=我不知道什么是AI工具或D10=听说过,但没有使用过
根因分析:
- D10 的值标签被多选题默认值
{0: '否', 1: '是'}覆盖 - 多选题默认值标签被错误地添加到了 D10
修复方案:
- 修复值标签解析逻辑,确保 D10 的值标签正确解析
- 多选题默认值只添加到真正的多选题变量(有子题号如 _A1, _A2)
修复效果:
| 变量 | 修复前 | 修复后 |
|---|---|---|
| D10 | D10=是或D10=听说过... |
D10=我不知道什么是AI工具或D10=听说过... |
修复问题2:多选题数据逻辑定义规则不完善
- 场景1:dim_value 表示"是"的情况(如"去哪儿")→ 输出
=是 - 场景2:dim_value 表示"否"的情况(如"以上都没有")→ 输出
=否 - 场景3:definition 包含"≠"(如"D8_A≠99")→ 输出
=否
修复方案:
- 关键词判断:dim_value 包含"没有"、"未"、"无"、"以上都没有"等表示"否"
- 符号判断:definition 包含"≠"表示"否"
- 综合判断:结合关键词和符号判断"是"或"否"
修复效果:
| 场景 | 示例 | 修复后 |
|---|---|---|
| dim_value="去哪儿" | D6=2 | D6_2=是 |
| dim_value="以上都没有" | D8_A≠99 | D8_SQ1_99=否 |
| definition 含"≠" | D8_A≠99 | D8_SQ1_99=否 |
v2.1.5 重要改进
1. 值标签解析完整性修复(2026-03-31)
修复问题:值标签解析失败,解析结果为0条
根因分析:
- 实际文件格式:
│变量名(大量空格)code│标签│,code位于行尾 - 原正则
(\d+\.\d+|\.\d+)\s+(.+)$期望code后有内容,但实际code在行尾 - 原逻辑无法正确处理这种格式
修复方案:
- 改进正则:使用
(\d+\.\d+)\s*$从行尾匹配code - 改进变量名提取:
first_part[:match.start()].strip()提取code前的变量名 - 正确处理空变量名行:继承上一个变量名
修复效果:
- 值标签解析:0条 → 1092条
- 涉及变量:0个 → 96个
2. Banner表结构检测修复(2026-03-31)
修复问题:2行/3行结构检测错误,导致维度信息混乱
根因分析:
- 原检测逻辑:检查
row_plus_1是否包含=或≠ - 问题:Banner2的2行结构中,
row_plus_1就是definition(含=),被误判为3行结构 - 结果:dim_value和definition错位,dim_type提取错误
修复方案:
- 多维度检测:
- 检查
row_plus_1是 dim_value(纯中文)还是 definition(含=) - 如果是 dim_value,再检查
row_plus_2是否有 definition
- 检查
- 正确区分结构:
- 3行结构:dim_type_row = total_row, dim_value_row = total_row+1, def_row = total_row+2
- 2行结构:dim_type_row = None, dim_value_row = total_row, def_row = total_row+1
- 正确提取dim_type:3行结构从第2列开始查找(跳过Total列)
修复效果:
- 维度信息格式:从
Banner1-人口学--城市级别修复为Banner1-人口学-城市级别-一线城市 - 条件列:正确填充
- 需求定义:正确定义
- 自动匹配率:从 17.9% 提升到 61.0%
3. 多级题号匹配(D8-1, D8-2等)(原有)
场景:Banner7-联系过智能/电话自助/人工
| Definition | dim_value | 匹配变量 |
|---|---|---|
| D8-1_A=1 | 在线智能-携程 | D8-1_SQ1_A1_您使用的在线客服是以下哪种形式?-携程:在线智能/机器客服 |
| D8-2_A=1 | 电话自助-携程 | D8-2_SQ1_A1_您使用的电话客服是以下哪种形式?-携程:电话语音自助客服 |
匹配逻辑:
# 提取基础题号和子题号
base_code = "D8-1" # 基础题号
sub_code = "A" # 子题号
# 查找候选变量
for var_name in variables:
if var_name.startswith(base_code + "_"):
# 检查平台匹配(携程→SQ1)
# 检查分类匹配(在线智能/电话自助)
# 检查子题号匹配(A→A1)
4. 值标签反向匹配(原有)
场景:D10=1/2 且 dim_value="没有用过AI"
匹配逻辑:
# 1. 查找候选变量(以D10开头)
candidates = [var for var in variables if var.startswith("D10_")]
# 2. 检查code 1/2对应的值标签
for var in candidates:
label_1 = value_labels[var]["1"] # "我不知道什么是AI工具"
label_2 = value_labels[var]["2"] # "听说过,但没有使用过"
# 3. 检查值标签是否与dim_value匹配
if "没有" in label_1 and "没有" in dim_value:
# 匹配成功
关键词映射:
| dim_value关键词 | 值标签匹配词 |
|---|---|
| 没有用过 | 不知道、没有使用、没用过 |
| 使用过 | 使用过、用过、正在使用 |
| 低频 | 偶尔、有时、很少 |
| 高频 | 经常、频繁、总是 |
5. 复合维度值匹配优化(原有)
场景:Q7_A=1 且 dim_value="携程-使用过"
匹配逻辑:
# 1. 提取平台和状态
platform = "携程" # 从dim_value提取
status = "使用过" # 从dim_value提取
# 2. 查找候选变量
for var_name in variables:
if var_name.startswith("Q7_") and "携程" in var_label:
# 3. 检查code 1对应的值标签是否为"使用过"
if value_labels[var_name]["1"] == "使用过":
# 匹配成功: Q7_SQ1_最近一年...
变量匹配准确率提升
新增匹配策略:
1. 多级题号匹配(D8-1, D8-2等)
场景:Banner7-联系过智能/电话自助/人工
| Definition | dim_value | 匹配变量 |
|---|---|---|
| D8-1_A=1 | 在线智能-携程 | D8-1_SQ1_A1_您使用的在线客服是以下哪种形式?-携程:在线智能/机器客服 |
| D8-2_A=1 | 电话自助-携程 | D8-2_SQ1_A1_您使用的电话客服是以下哪种形式?-携程:电话语音自助客服 |
匹配逻辑:
# 提取基础题号和子题号
base_code = "D8-1" # 基础题号
sub_code = "A" # 子题号
# 查找候选变量
for var_name in variables:
if var_name.startswith(base_code + "_"):
# 检查平台匹配(携程→SQ1)
# 检查分类匹配(在线智能/电话自助)
# 检查子题号匹配(A→A1)
2. 值标签反向匹配
场景:D10=1/2 且 dim_value="没有用过AI"
匹配逻辑:
# 1. 查找候选变量(以D10开头)
candidates = [var for var in variables if var.startswith("D10_")]
# 2. 检查code 1/2对应的值标签
for var in candidates:
label_1 = value_labels[var]["1"] # "我不知道什么是AI工具"
label_2 = value_labels[var]["2"] # "听说过,但没有使用过"
# 3. 检查值标签是否与dim_value匹配
if "没有" in label_1 and "没有" in dim_value:
# 匹配成功
关键词映射:
| dim_value关键词 | 值标签匹配词 |
|---|---|
| 没有用过 | 不知道、没有使用、没用过 |
| 使用过 | 使用过、用过、正在使用 |
| 低频 | 偶尔、有时、很少 |
| 高频 | 经常、频繁、总是 |
3. 复合维度值匹配优化
场景:Q7_A=1 且 dim_value="携程-使用过"
匹配逻辑:
# 1. 提取平台和状态
platform = "携程" # 从dim_value提取
status = "使用过" # 从dim_value提取
# 2. 查找候选变量
for var_name in variables:
if var_name.startswith("Q7_") and "携程" in var_label:
# 3. 检查code 1对应的值标签是否为"使用过"
if value_labels[var_name]["1"] == "使用过":
# 匹配成功: Q7_SQ1_最近一年...
改进效果:
- 匹配率:从 71% → 目标 85%+
- 匹配准确率:显著提升(减少误匹配)
- 复杂条件处理:支持多级题号、值标签反向验证
v2.1.4 重要改进
需求定义输出格式优化
改进背景:用户反馈需求定义列应直接输出原始Definition,而非拼接中文描述
改进前:
- 输出:
城市级别-一线城市是D4a=1 - 问题:包含中文描述前缀,不符合用户需求
改进后:
- 输出:
D4a=1 - 优势:直接使用原始Definition,简洁清晰
复杂条件支持:
| 原始Definition | 改进前输出 | 改进后输出 |
|---|---|---|
| D9_A仅选中1 | 售前&售后-携程是D9_A仅选中1 | D9_A仅选中1 |
| D10=1/2 | 没有用过AI-D10=1/2 | D10=1/2 |
| D9_A选中1,且选中2/3/4 | 售前&售后-携程是D9_A选中1,且选中2/3/4 | D9_A选中1,且选中2/3/4 |
Banner表结构自适应检测
问题:不同Banner采用不同的表结构(2行 vs 3行)
解决方案:
# 检测方法:检查Row+1的Col3是否包含=或≠
if row_plus_1_has_def:
dim_type_row = total_row # 3行结构
dim_value_row = total_row + 1
definition_row = total_row + 2
else:
dim_type_row = None # 2行结构
dim_value_row = total_row
definition_row = total_row + 1
改进效果:
- 匹配率:从 50% → 71% (+21%)
- 需求定义准确性:大幅提升
- 复杂条件处理:正确保留原始格式
v2.1.3 重要修复
值标签解析完整性修复
修复问题:值标签表解析不完整,遗漏大量变量
根因分析:
- 正则表达式
(\d+\.?\d*)无法匹配.00格式(前面没有数字) - 依赖分隔线(├─)判断变量分组,但文件中很多变量连续排列无分隔线
- 变量名继承逻辑在遇到分隔线时重置,导致后续变量丢失
修复方案:
- 改进正则:使用
((?:\d+)?\.\d+)支持可选数字前缀的小数(如.00,1.00) - 改进分组逻辑:通过变量名存在性判断新变量开始,不依赖分隔线
- 保留原始code:对于
.00格式,可选择保留或标准化为0
修复效果:
- 变量数量:从 96个 → 692个(+620%)
- 值标签条目:从 1092条 → 完整提取
- 遗漏变量示例:D1_A1-D1_A8(8个变量,之前全部遗漏)
代码变更:
- 更新函数:
parse_value_sheet_fixed()→parse_value_sheet_fixed_v213() - 关键改进:
# 旧正则(不支持 .00) match = re.search(r'(\d+\.?\d*)\s+([^\d].+)$', content) # 新正则(支持 .00) match = re.search(r'((?:\d+)?\.\d+)\s+([^\d].+)$', content) # 旧分组逻辑(依赖分隔线) if line.startswith('├'): # 分隔线表示变量结束 # 新分组逻辑(通过变量名存在性) if var_name and var_name != '': # 变量名存在表示新变量
v2.1.2 新增功能
数据逻辑定义智能转换
新增功能:数据逻辑定义自动转换为中文标签
转换规则:
| 输入 | 值标签 | 输出 |
|---|---|---|
| D2=1 | {1: '男性', 2: '女性'} | D2=男性 |
| D3a=3/4 | {3: '25-29岁', 4: '30-34岁'} | D3a=25-29岁或D3a=30-34岁 |
| D7_E=3 | {0: '否', 1: '是'} | D7_5_3=是 |
转换逻辑:
- 从值标签表查找code对应的中文标签
/转换为"或"(多选),转换为"且"(同时满足)- 多选题自动识别,输出"=是"
v2.1.1 新增功能
通用多级嵌套匹配
新增匹配能力:基于多维度信息交叉验证的通用匹配方法
匹配要素:
- 题号前缀:从Definition提取(如 D8)
- 一级分类:从维度类型提取(如 在线客服)
- 平台+二级分类:从维度值提取(如 携程-在线)
- Code:从Definition提取(如 1)
匹配示例:
| Definition | 维度类型 | 维度值 | 匹配变量 |
|---|---|---|---|
| D8_A=1 | 在线客服 | 携程-在线 | D8_SQ1_1 |
| D8_B=1 | 在线客服 | 去哪儿-在线 | D8_SQ2_1 |
| D8_A=2 | 电话客服 | 携程-电话 | D8_SQ1_2 |
| D8-1_A=1 | 在线智能 | 携程-在线智能 | D8_1_SQ1_1 |
匹配逻辑:
变量名以题号前缀开头 (D8*)
AND 变量标签包含平台名 (携程)
AND 变量标签包含分类关键词 (在线客服)
AND 变量名结尾匹配code (_1)
→ 最佳匹配: D8_SQ1_1
适用场景:Banner7-联系过智能/电话自助/人工 等复杂嵌套结构
v2.1.0 重要修复
修复问题
| 问题 | 根因 | 修复方案 |
|---|---|---|
| Banner解析不完整 | 错误理解Total行结构,维度类型行定位错误 | Total行同时是维度类型行,添加前向填充逻辑 |
| 需求信息格式错误 | 混淆维度类型和维度值 | 正确解析三行结构(类型/值/Definition) |
| 变量标签截断 | 代码中有 [:100] 截断 |
移除截断,输出完整标签 |
| 值标签不完整 | code行变量名为空时未继承 | 使用 current_var 跟踪确保code正确关联 |
| 匹配率低 | 只支持简单变量匹配 | 新增子变量、复合维度值、三级题号匹配 |
修复效果
- Banner解析:从部分输出 → 完整13个Banner
- 匹配率:从 27.54% → 78.26% (+50.72%)
- 标签输出:从100字符截断 → 完整输出
5步处理流程概览
┌─────────────────────────────────────────────────────────────────┐
│ 5步标准化处理流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 【Step 1: 格式识别与解析】 │
│ ├─ 自动检测输入文件格式(SPSS编码表/Banner表/问卷定义表) │
│ ├─ 解析三个核心Sheet:Banner表、变量表、值标签表 │
│ └─ 提取维度需求、变量定义、值标签映射 │
│ ↓ │
│ 【Step 2: 数据清洗与标准化】 │
│ ├─ 去除无效数据(分隔线、空行、表头) │
│ ├─ 标准化变量名(统一大小写、去除空格) │
│ ├─ 数值解析(从右向左查找code值) │
│ └─ 处理混合结构(类型A/B识别) │
│ ↓ │
│ 【Step 3: 数据整理】 │
│ ├─ 条件拆分(&符号→且,|符号→或) │
│ ├─ 生成需求定义(条件组合) │
│ └─ 结构化存储(便于后续匹配) │
│ ↓ │
│ 【Step 4: 维度匹配与定义生成】 │
│ ├─ 三级匹配策略: │
│ │ ① 从Definition精确提取变量名(如D4a=1→D4A) │
│ │ ② 精确匹配维度类型到变量表 │
│ │ ③ 模糊匹配(相似度算法) │
│ ├─ 生成数据逻辑定义(展开范围、连接符号) │
│ ├─ 生成新变量名(banner{序号}_{维度序号}_{值序号}) │
│ └─ 生成code(每个维度内从1递增) │
│ ↓ │
│ 【Step 5: 质量检查与报告】 │
│ ├─ 5.1 完整性检查(未匹配项、缺失值标签、不完整逻辑) │
│ ├─ 5.2 来源验证(验证匹配结果是否来自源表) │
│ ├─ 5.3 匹配率计算(自动/模糊/需审核分类统计) │
│ ├─ 5.4 输出文件(待审核版 + 最终版) │
│ └─ 5.5 处理报告(Markdown格式详细报告) │
│ │
└─────────────────────────────────────────────────────────────────┘
详细处理步骤
Step 1: 格式识别与解析
1.1 检测文件格式
| 格式类型 | 检测特征 | 处理方法 |
|---|---|---|
| SPSS编码表 | 包含"变量"和"值标签"sheet | 解析变量表和值标签表 |
| Banner横向交叉表 | 多列结构,Banner在行 | 横向解析,提取维度层级 |
| Banner纵向列表 | 少列结构,Banner在列 | 纵向解析,提取定义 |
1.2 解析三个核心Sheet
def step1_parse_files(file_path):
"""Step 1: 解析三个Sheet"""
wb = openpyxl.load_workbook(file_path)
# 1.1 解析Banner表
ws_banner = wb['Banner']
banner_data = parse_banner_sheet(ws_banner)
# 1.2 解析变量表(SPSS格式)
ws_var = wb['变量']
var_data = parse_var_sheet(ws_var)
# 1.3 解析值标签表(SPSS格式)
ws_value = wb['值标签']
value_data = parse_value_sheet(ws_value)
return banner_data, var_data, value_data
1.3 Banner表解析(处理混合结构)
Banner表存在两种结构类型:
- 类型A: dim_type是维度类型(如"城市级别"),dim_value_def是Definition(如"D4a=1")
- 类型B: dim_type本身就是Definition(如"D10=1/2")
def parse_banner_sheet_v3(ws):
"""
解析Banner表(v2.1.0修复版)
关键修复:
1. Total行(行26)同时也是维度类型行,不是下一行
2. 维度类型行需要前向填充(继承左边最近的维度类型)
3. 正确区分维度类型、维度值、Definition三行
结构:
- 行25: Banner标题
- 行26: Total + 维度类型(稀疏填充,列3="是否联系过平台客服",列8="经服业务线-携程")
- 行27: 维度值(列3="携程",列8="酒店-携程")
- 行28: Definition(列3="D8_A≠99",列8="D7_A=1")
"""
items = []
row_idx = 1
while row_idx <= ws.max_row:
first_cell = ws.cell(row_idx, 1).value
if first_cell and str(first_cell).startswith('Banner'):
current_banner = str(first_cell).strip()
# 查找Total行
total_row = None
for check_row in range(row_idx + 1, min(row_idx + 5, ws.max_row + 1)):
cell2 = ws.cell(check_row, 2).value
if cell2 and str(cell2).strip() == 'Total':
total_row = check_row
break
if not total_row:
row_idx += 1
continue
# 关键修复:Total行就是维度类型行
dim_type_row = total_row
value_row = total_row + 1
def_row = total_row + 2
# 读取三行数据
dim_types = {}
dim_values = {}
definitions = {}
for col in range(2, ws.max_column + 1):
dim_types[col] = ws.cell(dim_type_row, col).value
dim_values[col] = ws.cell(value_row, col).value
definitions[col] = ws.cell(def_row, col).value
# 关键修复:前向填充维度类型
last_type = None
for col in sorted(dim_types.keys()):
if dim_types[col]:
last_type = str(dim_types[col]).strip()
elif last_type and dim_values[col]:
# 继承左边最近的维度类型
dim_types[col] = last_type
# 生成条目
for col in dim_values.keys():
if dim_values[col]:
dim_type = str(dim_types[col]).strip() if dim_types[col] else ""
dim_value = str(dim_values[col]).strip()
definition = str(definitions[col]).strip() if definitions[col] else ""
# 判断结构类型
if '=' in dim_type:
# 类型B: dim_type就是Definition
items.append({
'banner': current_banner,
'dim_type': dim_value,
'dim_value': dim_value,
'definition': dim_type,
'row': row_idx,
'col': col
})
else:
# 类型A: 正常结构
items.append({
'banner': current_banner,
'dim_type': dim_type,
'dim_value': dim_value,
'definition': definition,
'row': row_idx,
'col': col
})
row_idx = def_row + 1
else:
row_idx += 1
return items
1.4 变量表解析(SPSS格式)
def parse_var_sheet(ws):
"""解析变量表(SPSS导出格式)"""
variables = {}
for row in ws.iter_rows(min_row=5, values_only=True):
if not row or len(row) < 1:
continue
row_text = str(row[0]) if row[0] else ""
# 跳过分隔线
if any(c in row_text for c in ['─', '├', '╭', '╰']):
continue
# 解析SPSS格式:│名称│位置│标签│...
match = re.search(r'│\s*(\w+)\s*│\s*\d+\s*│\s*([^│]+)│', row_text)
if match:
var_name = match.group(1).strip()
var_label = match.group(2).strip()
if var_name and var_name != '名称':
variables[var_name] = {
'name': var_name,
'label': var_label
}
return variables
1.5 值标签表解析(SPSS格式 - v2.1.3修复版)
⚠️ 重要修复:处理单行混合格式,确保所有code都被正确解析
v2.1.3 修复内容:
- 支持
.00格式:处理值标签为.00(前面没有数字)的情况,如.00 否 - 改进变量分组逻辑:不依赖分隔线,通过变量名存在性判断新变量开始
- 更健壮的code提取:支持
(\d+)?\.\d+格式(可选数字前缀的小数)
def parse_value_sheet_fixed(ws):
"""
解析值标签表(SPSS导出格式 - v2.1.3修复版)
修复问题:
1. 处理单行混合格式(变量名和code在同一行)
2. 继承变量名(code行变量名为空时使用上一个变量名)
3. 确保所有code都被正确关联到对应变量
4. 【v2.1.3新增】支持 .00 格式(如 .00 否)
5. 【v2.1.3新增】不依赖分隔线,正确处理连续变量
输入格式示例:
│D2_您的性别是 1.00 男性│
│ 2.00 女性│
│D1_A1_请问您最近3个月是否接受过以下品类的市场调查?:旅行/出行类 .00 否│
│ 1.00 是│
"""
value_labels = {}
current_var = None
for i in range(5, ws.max_row + 1):
cell_value = ws.cell(i, 1).value
if not cell_value:
continue
row_text = str(cell_value)
# 跳过分隔线
if any(c in row_text for c in ['─', '├', '╭', '╰']) or 'Variable' in row_text:
continue
# 去除│和前后空格
content = row_text.replace('│', '').rstrip()
if not content:
continue
# 【v2.1.3修复】从右向左查找code模式
# 支持格式:1.00, 2.00, .00(前面没有数字)
# 正则:(\d+)?\.\d+ 表示可选的数字前缀 + 小数点 + 数字
match = re.search(r'((?:\d+)?\.\d+)\s+([^\d].+)$', content)
if match:
code = match.group(1).strip()
label = match.group(2).strip()
# 提取变量名(code之前的所有内容)
var_end = content.rfind(match.group(0))
var_name = content[:var_end].strip()
# 【v2.1.3修复】标准化code
# 对于 .00 格式,保留原始格式或转换为 0
if code.startswith('.'):
code_normalized = '0' # 或者保留 code = code
else:
code_normalized = str(int(float(code)))
# 【v2.1.3修复】关键逻辑:通过变量名存在性判断新变量
# 如果变量名不为空,说明是新变量的开始
if var_name and var_name != '':
# 保存之前的变量(如果有)
if current_var and current_var in value_labels and value_labels[current_var]:
pass # 已保存
current_var = var_name
if current_var not in value_labels:
value_labels[current_var] = {}
# 使用current_var确保code被正确关联
if current_var:
if current_var not in value_labels:
value_labels[current_var] = {}
value_labels[current_var][code_normalized] = label
return value_labels
v2.1.3 关键改进说明:
| 问题 | v2.1.2及之前 | v2.1.3修复 |
|---|---|---|
.00 格式 |
无法识别 .00 否 |
支持 (\d+)?\.\d+ 正则,正确识别 |
| 变量分组 | 依赖分隔线(├─)分组 | 通过变量名存在性判断,不依赖分隔线 |
| 连续变量 | 分隔线后误认为是新变量 | 正确处理连续排列的变量 |
| 变量数量 | 96个(遗漏大量变量) | 692个(完整提取) |
修复效果示例:
D1_A1变量(之前遗漏):
│D1_A1_请问您最近3个月是否接受过以下品类的市场调查?:旅行/出行类 .00 否│
│ 1.00 是│
- v2.1.2: ❌ 未识别(.00格式不支持)
- v2.1.3: ✅ 正确识别,提取2个值标签(.00→否, 1.00→是)
数据通道变量(之前被截断):
│数据通道 2.00 │私域通道│
│ 3.00 │第三方通道│
...(连续48个值标签,无分隔线)
-
v2.1.2: ❌ 只提取4个(遇到分隔线后停止)
-
v2.1.3: ✅ 完整提取48个值标签
return value_labels
### Step 2: 数据清洗与标准化
**2.1 清洗规则**
```python
def step2_clean_data(banner_data, var_data, value_data):
"""Step 2: 数据清洗与标准化"""
# 清洗Banner数据
cleaned_banner = []
for item in banner_data:
if not item['dim_type']:
continue
# 标准化
item['dim_type'] = item['dim_type'].strip()
item['dim_value'] = item['dim_value'].strip().replace('\n', ' ') if item['dim_value'] else item['dim_type']
item['definition'] = item['definition'].strip() if item['definition'] else None
cleaned_banner.append(item)
# 标准化变量名(大写)
cleaned_var = {}
for var_name, var_info in var_data.items():
clean_name = var_name.upper().strip()
cleaned_var[clean_name] = var_info
# 标准化值标签
cleaned_value = {}
for var_name, codes in value_data.items():
clean_name = var_name.upper().strip()
cleaned_value[clean_name] = {}
for code, label in codes.items():
clean_code = str(code).strip()
cleaned_value[clean_name][clean_code] = label.strip()
return cleaned_banner, cleaned_var, cleaned_value
Step 3: 数据整理
3.0 Banner表解析(v2.1.4重要改进)
⚠️ 重要改进:自适应检测Banner表结构(2行 vs 3行)
问题背景:不同Banner采用不同的表结构
- 3行结构(如Banner1): Total行=dim_type, 下一行=dim_value, 再下一行=definition
- 2行结构(如Banner2): Total行=dim_value, 下一行=definition
检测方法:检查Total行的下一行(Row+1)的Col3是否包含=或≠
- 如果包含 → 3行结构(Row+1是dim_value,包含definition格式)
- 如果不包含 → 2行结构(Row+1是definition)
def parse_banner_sheet_adaptive(ws):
"""
自适应解析Banner表(v2.1.4改进版)
支持两种结构:
1. 3行结构: dim_type行 → dim_value行 → definition行
2. 2行结构: dim_value行(Total行) → definition行
"""
items = []
row_idx = 1
while row_idx <= ws.max_row:
first_cell = ws.cell(row_idx, 1).value
if first_cell and str(first_cell).startswith('Banner'):
current_banner = str(first_cell).strip()
# 查找Total行
total_row = None
for check_row in range(row_idx + 1, min(row_idx + 5, ws.max_row + 1)):
cell2 = ws.cell(check_row, 2).value
if cell2 and str(cell2).strip() == 'Total':
total_row = check_row
break
if not total_row:
row_idx += 1
continue
# 【v2.1.4关键改进】自适应检测结构
# 检查Row+1的Col3是否包含=或≠
row_plus_1 = total_row + 1
row_plus_1_has_def = False
for col in range(3, 20):
val = ws.cell(row_plus_1, col).value
if val and ('=' in str(val) or '≠' in str(val)):
row_plus_1_has_def = True
break
if row_plus_1_has_def:
# 3行结构
dim_type_row = total_row
dim_value_row = total_row + 1
definition_row = total_row + 2
else:
# 2行结构
dim_type_row = None
dim_value_row = total_row
definition_row = total_row + 1
# 读取数据...
# (后续逻辑与之前版本相同)
row_idx = definition_row + 1
else:
row_idx += 1
return items
3.1 条件拆分
def step3_organize_data(banner_data):
"""Step 3: 数据整理"""
requirements = []
for item in banner_data:
req = {
'banner': item['banner'],
'dim_type': item['dim_type'],
'dim_value': item['dim_value'],
'original_def': item.get('definition'),
'conditions': []
}
# 3.2 条件拆分
dim_type = item['dim_type']
# 处理 & 符号(且)
if '&' in dim_type:
parts = dim_type.split('&')
for i, part in enumerate(parts, 1):
req['conditions'].append({
'condition_col': f'条件{i}',
'type': 'and',
'value': part.strip()
})
# 处理 | 符号(或)
elif '|' in dim_type and '=' not in dim_type:
parts = dim_type.split('|')
for i, part in enumerate(parts, 1):
req['conditions'].append({
'condition_col': f'条件{i}',
'type': 'or',
'value': part.strip()
})
# 单条件
else:
req['conditions'].append({
'condition_col': '条件1',
'type': 'single',
'value': dim_type
})
# 3.3 生成需求定义(v2.1.4改进)
# 直接使用definition列的内容,不进行额外处理
if req['original_def']:
req['demand_def'] = req['original_def']
else:
req['demand_def'] = ""
requirements.append(req)
return requirements
v2.1.4 改进说明:
| 项目 | v2.1.3及之前 | v2.1.4改进 |
|---|---|---|
| Banner结构 | 固定3行结构 | 自适应2行/3行结构 |
| 需求定义 | 拼接中文描述(如"城市级别-一线城市是D4a=1") | 直接使用definition(如"D4a=1") |
| 复杂条件 | 无法正确处理 | 保留原始格式(如"D9_A仅选中1") |
| 匹配率 | 50-60% | 70%+ |
Step 4: 维度匹配与定义生成
4.0 数据逻辑定义生成函数(v2.1.6 修复版)
def _get_value_label(self, var_name: str, code: str) -> Optional[str]:
"""
获取指定变量和code对应的值标签
支持简写变量名映射(如 D2 -> D2_您的性别是)
"""
if var_name in self.var_value_dict and code in self.var_value_dict[var_name]:
return self.var_value_dict[var_name][code]
elif '_' in var_name:
# 尝试简写名称(第一个下划线之前的部分)
short_name = var_name.split('_')[0]
if short_name in self.var_name_mapping:
full_name = self.var_name_mapping[short_name]
if full_name in self.var_value_dict and code in self.var_value_dict[full_name]:
return self.var_value_dict[full_name][code]
elif var_name in self.var_name_mapping:
# var_name 本身就是简写名称
full_name = self.var_name_mapping[var_name]
if full_name in self.var_value_dict and code in self.var_value_dict[full_name]:
return self.var_value_dict[full_name][code]
return None
def _generate_data_logic(self, var_name: str, definition: str) -> str:
"""
生成数据逻辑定义(v2.1.6 修复版)
规则:
1. 使用匹配到的 var_name(不是 definition 中的变量前缀)查找值标签
2. 支持多个code值,用"或"或"且"连接
3. 如果能找到值标签,输出:var_name=值标签
4. 如果找不到值标签,输出:var_name=codeX
连接符规则:
- / 表示"或"
- & 或 , 表示"且"
示例:
- var_name='D2', definition='D2=1' -> 'D2=男性'
- var_name='D3a', definition='D3a=3/4' -> 'D3a=25-29岁或D3a=30-34岁'
- var_name='F5', definition='F5=5/6/7' -> 'F5=51-100万元或F5=101-200万元或F5=200万元以上'
- var_name='D4_L2_城市级别', definition='D4a=1' -> 'D4_L2_城市级别=code1'
"""
if not var_name or not definition:
return ''
# 从 definition 中提取 codes 和连接符
codes = []
connector = '' # '或' 或 '且'
if '≠' in definition:
parts = definition.split('≠')
if len(parts) == 2:
code = parts[1].strip()
codes = [code]
elif '=' in definition:
match = re.match(r'(\w+)=(.+)', str(definition))
if match:
codes_part = match.group(2)
# 判断连接符:/ 表示"或",& 或 , 表示"且"
if '/' in codes_part:
codes = [c.strip() for c in codes_part.split('/')]
connector = '或'
elif '&' in codes_part:
codes = [c.strip() for c in codes_part.split('&')]
connector = '且'
elif '|' in codes_part:
codes = [c.strip() for c in codes_part.split('|')]
connector = '或'
else:
codes = [codes_part.strip()]
if not codes:
return definition
# 为每个 code 查找值标签
value_parts = []
for code in codes:
value_label = self._get_value_label(var_name, code)
if value_label:
value_parts.append(f"{var_name}={value_label}")
else:
value_parts.append(f"{var_name}=code{code}")
# 用连接符连接多个值
if len(value_parts) > 1 and connector:
return connector.join(value_parts)
elif value_parts:
return value_parts[0]
else:
return definition
关键修复说明(v2.1.6):
-
使用匹配到的 var_name 查找值标签:不是使用 definition 中的变量前缀(如 D4a),而是使用匹配到的实际变量名(如 D4_L2_城市级别)
-
支持简写变量名映射:值标签表中的变量名通常是完整形式(如 D2_您的性别是),而匹配到的变量名可能是简写形式(如 D2)。通过
var_name_mapping建立映射关系 -
支持多 code 值连接:
/分隔的 code 用 "或" 连接&或,分隔的 code 用 "且" 连接
-
区分两种情况:
- 能找到值标签 → 输出
var_name=值标签 - 找不到值标签 → 输出
var_name=codeX
- 能找到值标签 → 输出
4.1 增强匹配策略(修复版)
⚠️ 重要改进:新增复合维度值匹配、三级题号匹配、语义一致性验证
def step4_match_dimensions(requirements, var_data, value_data):
"""
Step 4: 维度匹配与定义生成(增强版)
匹配策略(按优先级):
1. 直接匹配:从Definition精确提取变量名
2. 子变量匹配:题号+code匹配(如D6=1→D6_1)
3. 复合维度值匹配:处理"酒店-携程"等多部分维度值
4. 三级题号匹配:处理D7_A=1→D7_1_1等复杂题号
5. 语义一致性验证:验证业务语义匹配度
"""
for req in requirements:
original_def = req.get('original_def', '')
dim_value = req['dim_value']
dim_type = req['dim_type']
matched_var = None
match_type = '需人工审核'
confidence = '0%'
if original_def and '=' in original_def:
# 提取题号(支持多级如 D7_A, Q20_A)
var_candidate = original_def.split('=')[0].strip()
code_value = original_def.split('=')[1].strip() if '=' in original_def else ''
# 第一级:直接匹配(如 D2, F5 等简单变量)
if var_candidate.upper() in var_data:
matched_var = var_candidate.upper()
match_type = '自动匹配'
confidence = '95%'
elif var_candidate in var_data:
matched_var = var_candidate
match_type = '自动匹配'
confidence = '95%'
else:
# 第二级:通用多级嵌套匹配(新增 v2.1.1)
# 基于多维度信息交叉验证的通用匹配方法
# 示例:D8_A=1 + 在线客服 + 携程-在线 → D8_SQ1_1
if '_' in original_def and '=' in original_def:
# 提取关键信息
def_prefix = original_def.split('_')[0] # D8
code_value = original_def.split('=')[1] # 1
# 从维度值提取平台和分类
platform = ''
category_keywords = [dim_type] # 一级分类
if '-' in dim_value:
parts = dim_value.split('-')
platform = parts[0] # 携程/去哪儿
if len(parts) > 1:
category_keywords.append(parts[1]) # 二级分类
else:
platform = dim_value
# 查找候选变量
candidates = []
for var_name, var_info in var_data.items():
# 条件1:变量名以题号前缀开头
if not var_name.startswith(def_prefix):
continue
label = var_info.get('label', '')
score = 0
# 条件2:变量标签包含平台名
if platform and platform in label:
score += 50
# 条件3:变量标签包含分类关键词
for keyword in category_keywords:
if keyword and keyword in label:
score += 30
# 条件4:code匹配(变量名结尾或标签中)
if var_name.endswith(f'_{code_value}') or f'A{code_value}' in var_name:
score += 40
if score > 0:
candidates.append((var_name, score, label))
# 选择最佳匹配
if candidates:
candidates.sort(key=lambda x: x[1], reverse=True)
best_match = candidates[0]
matched_var = best_match[0]
match_type = '自动匹配'
confidence = f'{min(best_match[1], 95)}%'
# 第三级:解析题号结构,查找子变量
# D7_A → 基础题号D7,子题号A
# Q20_A → Q20 和 A
if not matched_var:
base_match = re.match(r'^(\w+?)(_[A-Z])?$', var_candidate)
if base_match:
base_code = base_match.group(1) # D7 或 Q20
sub_code = base_match.group(2) # _A 或 None
# 查找所有相关变量
matched_vars = {}
for var_name, var_info in var_data.items():
if base_code in var_name or base_code.upper() in var_name.upper():
if sub_code:
# 子题号A可能对应 _1_ 或 A1
sub_letter = sub_code[1] # A
if f'_{sub_letter}' in var_name or f'_{ord(sub_letter) - ord("A") + 1}_' in var_name:
matched_vars[var_name] = var_info
else:
matched_vars[var_name] = var_info
if not matched_vars:
# 尝试简单的前缀匹配
for var_name, var_info in var_data.items():
if var_name.startswith(base_code + '_') or var_name.upper().startswith(base_code.upper() + '_'):
matched_vars[var_name] = var_info
if matched_vars:
# 第三级:用维度值匹配(支持复合维度值如"酒店-携程")
best_match = None
best_score = 0
# 拆分复合维度值
dim_parts = dim_value.split('-')
for var_name, var_info in matched_vars.items():
label = var_info['label']
score = 0
# 复合维度值匹配:所有部分都在标签中
all_parts_found = True
for part in dim_parts:
if part in label:
score += 50
else:
all_parts_found = False
if all_parts_found:
score += 100
# code匹配(=1 匹配 _1 或 A1)
if code_value:
code_patterns = [
f'_{code_value}_',
f'_{code_value}',
f'A{code_value}',
f'{code_value}_'
]
for pattern in code_patterns:
if pattern in var_name:
score += 50
break
# 语义一致性验证
semantic_keywords = ['平台', '客服', '联系', '问题', '产品', '服务']
matched_keywords = [kw for kw in semantic_keywords if kw in label or kw in dim_type]
score += len(matched_keywords) * 10
if score > best_score:
best_score = score
best_match = var_name
if best_match and best_score >= 150:
matched_var = best_match
match_type = '自动匹配'
confidence = f'{min(best_score, 95):.0f}%'
elif best_match and best_score >= 100:
matched_var = best_match
match_type = '模糊匹配'
confidence = f'{min(best_score, 75):.0f}%'
# 设置匹配结果
req['matched_var'] = matched_var
req['matched_var_label'] = var_data[matched_var].get('label', '') if matched_var else ''
req['match_type'] = match_type
req['confidence'] = confidence
# 4.3 生成数据逻辑定义(修复版 - 支持值标签转换)
req['logic_def'] = generate_logic_definition(original_def, matched_var, value_data)
# 4.4 生成新变量名和Code
banner_counters = {}
for req in requirements:
banner = req['banner']
dim_type = req['dim_type']
if banner not in banner_counters:
banner_counters[banner] = {'dim_idx': 0, 'last_dim_type': None}
counter = banner_counters[banner]
if dim_type != counter['last_dim_type']:
counter['dim_idx'] += 1
counter['value_idx'] = 1
counter['last_dim_type'] = dim_type
else:
counter['value_idx'] += 1
# 提取banner序号
banner_num = re.search(r'Banner(\d+)', banner)
banner_idx = banner_num.group(1) if banner_num else '1'
req['new_var_name'] = f"banner{banner_idx}_{counter['dim_idx']}_{counter['value_idx']}"
req['code'] = counter['value_idx']
return requirements
Step 5: 质量检查与报告
5.1 完整性检查
def check_completeness(requirements, var_data, value_data):
"""5.1 完整性检查"""
unmatched = [r for r in requirements if not r.get('matched_var')]
missing_value_labels = []
for req in requirements:
var_name = req.get('matched_var')
if var_name and var_name not in value_data:
missing_value_labels.append({
'var_name': var_name,
'dim_type': req['dim_type']
})
incomplete_logic = []
for req in requirements:
if req.get('logic_def') and '?' in req['logic_def']:
incomplete_logic.append({
'dim_type': req['dim_type'],
'dim_value': req['dim_value'],
'logic_def': req['logic_def']
})
return {
'total_items': len(requirements),
'unmatched_count': len(unmatched),
'missing_value_labels': missing_value_labels,
'incomplete_logic': incomplete_logic
}
5.2 来源验证
def verify_sources(requirements, var_data, value_data):
"""5.2 来源验证"""
valid = []
invalid = []
manual = []
for req in requirements:
var_name = req.get('matched_var')
if not var_name:
manual.append(req)
continue
if var_name not in var_data:
invalid.append({'req': req, 'reason': f'变量 {var_name} 不在变量表中'})
continue
valid.append(req)
return {
'valid_matches': valid,
'invalid_matches': invalid,
'manual_review_items': manual
}
5.3 匹配率计算
def calculate_match_rate(requirements):
"""5.3 匹配率计算"""
total = len(requirements)
auto = len([r for r in requirements if r.get('match_type') == '自动匹配'])
fuzzy = len([r for r in requirements if r.get('match_type') == '模糊匹配'])
manual = len([r for r in requirements if r.get('match_type') == '需人工审核'])
match_rate = (auto + fuzzy) / total * 100 if total > 0 else 0
return {
'total': total,
'auto_matched': auto,
'fuzzy_matched': fuzzy,
'manual_review': manual,
'match_rate': round(match_rate, 2)
}
5.4 输出文件生成
def generate_review_output(requirements, var_data, output_file):
"""5.4 生成待审核版(v2.1.0修复版)
关键修复:
1. 变量标签不截断,输出完整内容
2. 需求信息格式正确:Banner-维度类型-维度值
3. 包含变量表和值标签表作为参考
"""
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill
wb = Workbook()
# Sheet1: 待审核版
ws1 = wb.active
ws1.title = "待审核版"
headers = ['需求信息', '条件1', '条件2', '条件3', '需求定义', '变量名称', '变量标签',
'数据逻辑定义', '匹配状态', '置信度']
for col, header in enumerate(headers, 1):
cell = ws1.cell(1, col, header)
cell.font = Font(bold=True, color='FFFFFF')
cell.fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
for row_idx, req in enumerate(requirements, 2):
# 关键修复:正确的需求信息格式
demand_info = f"{req['banner']}-{req['dim_type']}-{req['dim_value']}"
ws1.cell(row_idx, 1, demand_info)
ws1.cell(row_idx, 2, req['conditions'][0]['value'] if len(req['conditions']) > 0 else '')
ws1.cell(row_idx, 3, req['conditions'][1]['value'] if len(req['conditions']) > 1 else '')
ws1.cell(row_idx, 4, req['conditions'][2]['value'] if len(req['conditions']) > 2 else '')
ws1.cell(row_idx, 5, req.get('demand_def', ''))
ws1.cell(row_idx, 6, req.get('matched_var', ''))
# 关键修复:不截断标签,输出完整内容
matched_var = req.get('matched_var')
if matched_var and matched_var in var_data:
ws1.cell(row_idx, 7, var_data[matched_var].get('label', ''))
else:
ws1.cell(row_idx, 7, req.get('matched_var_label', ''))
ws1.cell(row_idx, 8, req.get('logic_def', ''))
ws1.cell(row_idx, 9, req.get('match_type', '需人工审核'))
ws1.cell(row_idx, 10, req.get('confidence', '0%'))
wb.save(output_file)
def generate_final_output(requirements, output_file):
"""5.4 生成最终版"""
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill
wb = Workbook()
ws = wb.active
ws.title = "最终版"
# 表头
headers = ['需求信息', '条件1', '条件2', '条件3', '需求定义', '变量名称', '变量标签',
'数据逻辑定义', '新变量名', '标签', 'code']
for col, header in enumerate(headers, 1):
cell = ws.cell(1, col, header)
cell.font = Font(bold=True, color='FFFFFF')
cell.fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
# 数据
for row_idx, req in enumerate(requirements, 2):
ws.cell(row_idx, 1, f"{req['banner']}-{req['dim_type']}-{req['dim_value']}")
ws.cell(row_idx, 2, req['conditions'][0]['value'] if len(req['conditions']) > 0 else '')
ws.cell(row_idx, 3, req['conditions'][1]['value'] if len(req['conditions']) > 1 else '')
ws.cell(row_idx, 4, req['conditions'][2]['value'] if len(req['conditions']) > 2 else '')
ws.cell(row_idx, 5, req.get('demand_def', ''))
ws.cell(row_idx, 6, req.get('matched_var', ''))
ws.cell(row_idx, 7, req.get('matched_var_label', ''))
ws.cell(row_idx, 8, req.get('logic_def', ''))
ws.cell(row_idx, 9, req.get('new_var_name', ''))
ws.cell(row_idx, 10, req['dim_value'])
ws.cell(row_idx, 11, req.get('code', ''))
wb.save(output_file)
5.5 处理报告生成
def generate_report(requirements, completeness, verification, match_rate):
"""5.5 生成处理报告"""
report = f"""# 维度定义处理报告
## 处理摘要
- 处理时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
- 总条目数: {match_rate['total']}
- 自动匹配: {match_rate['auto_matched']} ({match_rate['auto_matched']/match_rate['total']*100:.1f}%)
- 模糊匹配: {match_rate['fuzzy_matched']} ({match_rate['fuzzy_matched']/match_rate['total']*100:.1f}%)
- 需人工审核: {match_rate['manual_review']} ({match_rate['manual_review']/match_rate['total']*100:.1f}%)
- **整体匹配率: {match_rate['match_rate']}%**
## 完整性检查
- 解析条目数: {completeness['total_items']}
- 未匹配变量数: {completeness['unmatched_count']}
- 缺失值标签变量: {len(completeness['missing_value_labels'])}
- 不完整逻辑定义: {len(completeness['incomplete_logic'])}
## 来源验证
- 有效匹配: {len(verification['valid_matches'])}
- 无效匹配: {len(verification['invalid_matches'])}
- 需人工审核: {len(verification['manual_review_items'])}
## 需人工审核项(前20条)
| 序号 | 需求信息 | 维度类型 | 维度值 | 建议操作 |
|:---|:---|:---|:---|:---|
"""
for i, req in enumerate(verification['manual_review_items'][:20], 1):
report += f"| {i} | {req['banner']}-{req['dim_type']}-{req['dim_value']} | {req['dim_type']} | {req['dim_value']} | 人工指定变量名 |\n"
report += """
## 输出文件
1. **待审核版**: 维度定义表-待审核版.xlsx
2. **最终版**: 维度定义表-最终版.xlsx
## 注意事项
- 请重点审核"需人工审核"的条目
- 确认变量名称是否正确
- 确认数据逻辑定义是否完整
- 建议人工审核后重新生成最终版
"""
return report
使用方法
完整处理流程(含人工审核环节)
┌─────────────────────────────────────────────────────────────────┐
│ 标准处理流程(含人工审核) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 【第一轮】生成审核文件 │
│ ├─ 输入:原始编码表(Banner表 + 变量表 + 值标签表) │
│ ├─ 执行:Step 1-5 完整流程 │
│ └─ 输出:审核文件(3个Sheet) │
│ ├─ Sheet1: 待审核版(需人工审核的内容) │
│ ├─ Sheet2: 变量表(原始变量定义,供参考) │
│ └─ Sheet3: 值标签表(原始值标签,供参考) │
│ ↓ │
│ 【人工审核】用户审核并修正 │
│ ├─ 审核并修正"变量名称"列 │
│ ├─ 审核并修正"数据逻辑定义"列 │
│ ├─ 补充缺失信息 │
│ └─ 保存为:审核反馈文件 │
│ ↓ │
│ 【第二轮】生成最终版 │
│ ├─ 输入:审核反馈文件 │
│ ├─ 执行:读取审核结果,更新匹配,生成最终版 │
│ └─ 输出:最终版维度定义表 │
│ ├─ 包含完整的新变量名、code、标签等 │
│ └─ 基于审核后的变量名查询变量标签 │
│ │
└─────────────────────────────────────────────────────────────────┘
为什么需要人工审核?
根据实际项目经验,以下情况必须人工审核:
-
变量名称匹配不确定
- 自动匹配可能选择错误的变量
- 多选题变量(如 Q5__1, Q5__2)需要人工确认
- 复杂维度(如城市线级、地区划分)需要人工指定变量
-
数据逻辑定义需要业务确认
- "划分"类型的维度需要明确具体逻辑
- 组合维度需要确认组合规则
- 范围维度需要展开为具体值
-
派生变量不在变量表中
- 如 D4a(城市级别)可能是派生变量
- 需要人工指定实际使用的变量名
第一轮:生成审核文件
def generate_review_file(requirements, var_data, value_data, output_file):
"""
生成审核文件(3个Sheet)
Sheet1: 待审核版
Sheet2: 变量表(供参考)
Sheet3: 值标签表(供参考)
"""
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill
wb = Workbook()
# Sheet1: 待审核版
ws1 = wb.active
ws1.title = "待审核版"
headers = ['需求信息', '条件1', '条件2', '条件3', '需求定义',
'变量名称', '变量标签', '数据逻辑定义', '匹配状态', '置信度']
for col, header in enumerate(headers, 1):
cell = ws1.cell(1, col, header)
cell.font = Font(bold=True, color='FFFFFF')
cell.fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
for row_idx, req in enumerate(requirements, 2):
ws1.cell(row_idx, 1, f"{req['banner']}-{req['dim_type']}-{req['dim_value']}")
ws1.cell(row_idx, 2, req['conditions'][0]['value'] if len(req['conditions']) > 0 else '')
ws1.cell(row_idx, 3, req['conditions'][1]['value'] if len(req['conditions']) > 1 else '')
ws1.cell(row_idx, 4, req['conditions'][2]['value'] if len(req['conditions']) > 2 else '')
ws1.cell(row_idx, 5, req.get('demand_def', ''))
ws1.cell(row_idx, 6, req.get('matched_var', '')) # 重点审核列
ws1.cell(row_idx, 7, req.get('matched_var_label', ''))
ws1.cell(row_idx, 8, req.get('logic_def', '')) # 重点审核列
ws1.cell(row_idx, 9, req.get('match_type', '需人工审核'))
ws1.cell(row_idx, 10, req.get('confidence', '0%'))
# Sheet2: 变量表(供参考)
ws2 = wb.create_sheet("变量表")
ws2.cell(1, 1, "变量名")
ws2.cell(1, 2, "变量标签")
for row_idx, (var_name, var_info) in enumerate(var_data.items(), 2):
ws2.cell(row_idx, 1, var_name)
ws2.cell(row_idx, 2, var_info.get('label', ''))
# Sheet3: 值标签表(供参考)
ws3 = wb.create_sheet("值标签表")
ws3.cell(1, 1, "变量名")
ws3.cell(1, 2, "Code")
ws3.cell(1, 3, "标签")
row_idx = 2
for var_name, codes in value_data.items():
for code, label in codes.items():
ws3.cell(row_idx, 1, var_name)
ws3.cell(row_idx, 2, code)
ws3.cell(row_idx, 3, label)
row_idx += 1
wb.save(output_file)
人工审核要点
审核文件列说明:
| 列名 | 说明 | 审核重点 |
|---|---|---|
| 需求信息 | 完整需求路径 | 确认无误 |
| 条件1-3 | 拆分后的条件 | 确认无误 |
| 需求定义 | 业务定义 | 参考 |
| 变量名称 | 自动匹配的变量 | 重点审核 |
| 变量标签 | 变量描述 | 辅助确认 |
| 数据逻辑定义 | 初步逻辑 | 重点审核 |
| 匹配状态 | 自动/模糊/需审核 | 参考 |
| 置信度 | 匹配置信度 | 参考 |
审核操作:
- 打开审核文件(3个Sheet)
- 在"待审核版"Sheet中审核"变量名称"和"数据逻辑定义"列
- 参考"变量表"和"值标签表"Sheet确认变量信息
- 修正错误的匹配
- 补充缺失的变量名
- 保存为审核反馈文件
第二轮:人工审核后处理(v2.1.7 新增)
处理流程:
- 读取人工审核后的文件 - 包含已审核的变量名称
- 匹配变量标签 - 根据变量名称从变量表查找对应标签(仅当变量标签列为空时)
- 生成数据逻辑定义 - 根据变量名称和值标签生成(仅当数据逻辑定义列为空时)
- 保留人工填写内容 - 如果已有内容,保持原样不变
关键规则:
- 变量标签列非空 → 跳过,保留人工填写内容
- 数据逻辑定义列非空 → 跳过,保留人工填写内容
- 仅当列为空时,才自动匹配/生成
def process_reviewed_file(review_file, encoding_file, output_file):
"""
处理人工审核后的文件(v2.1.7 新增)
流程:
1. 解析原始编码表(获取变量表和值标签表)
2. 读取人工审核后的文件
3. 对于每一行:
- 如果变量标签列为空,根据变量名称匹配变量标签
- 如果数据逻辑定义列为空,根据变量名称和值标签生成
- 如果已有内容,保持原样不变
4. 保存结果
"""
from openpyxl import load_workbook
import pandas as pd
# 初始化处理器
processor = DimensionDefinerPro()
processor.parse_spss_encoding(encoding_file)
# 构建变量名到变量标签的映射
var_label_map = dict(zip(
processor.variables_df['变量名称'],
processor.variables_df['变量标签']
))
# 读取人工审核后的文件
wb = load_workbook(review_file)
ws = wb['需求定义']
# 处理每一行
updated_var_label = 0
updated_logic = 0
skipped = 0
for row in range(2, ws.max_row + 1):
var_name = ws.cell(row, 6).value # 变量名称
existing_var_label = ws.cell(row, 7).value # 现有变量标签
existing_logic = ws.cell(row, 8).value # 现有数据逻辑定义
definition = ws.cell(row, 5).value # 需求定义
dim_value = ws.cell(row, 3).value # 条件2 (dim_value)
if not var_name:
continue
# 1. 匹配变量标签(如果为空)
if not existing_var_label and var_name in var_label_map:
ws.cell(row, 7).value = var_label_map[var_name]
updated_var_label += 1
# 2. 生成数据逻辑定义(如果为空且有需求定义)
if not existing_logic and definition:
data_logic = processor._generate_data_logic(
var_name, definition, dim_value or ''
)
if data_logic:
ws.cell(row, 8).value = data_logic
updated_logic += 1
if existing_var_label or existing_logic:
skipped += 1
# 保存结果
wb.save(output_file)
return {
'updated_var_label': updated_var_label,
'updated_logic': updated_logic,
'skipped': skipped
}
处理示例:
| 变量名称 | 原变量标签 | 处理后 | 原数据逻辑定义 | 处理后 |
|---|---|---|---|---|
| H1 | (空) | H1_城市级别映射关系... | (空) | H1=一线城市 |
| D10 | (空) | D10_您在工作或生活中... | (空) | D10=我不知道什么是AI工具... |
| D6_2 | 人工填写 | 保持不变 | (空) | D6_2=是 |
| D8_SQ1_99 | 人工填写 | 保持不变 | 人工填写 | 保持不变 |
第二轮(旧版):生成最终版
def generate_final_from_review(review_file, output_file):
"""
基于审核反馈文件生成最终版(旧版,已弃用)
"""
from openpyxl import load_workbook
# 读取审核结果
wb = load_workbook(review_file)
ws = wb['待审核版']
requirements = []
for row in ws.iter_rows(min_row=2, values_only=True):
req = {
'demand_info': row[0],
'condition1': row[1],
'condition2': row[2],
'condition3': row[3],
'demand_def': row[4],
'matched_var': row[5], # 使用审核后的变量名
'var_label': row[6],
'logic_def': row[7], # 使用审核后的逻辑定义
'match_type': row[8],
'confidence': row[9]
}
requirements.append(req)
# 生成新变量名和code
banner_counters = {}
for req in requirements:
# 解析banner信息
banner_match = re.search(r'(Banner\d+)', req['demand_info'])
banner = banner_match.group(1) if banner_match else 'Banner1'
if banner not in banner_counters:
banner_counters[banner] = {'dim_idx': 0, 'last_cond': None}
counter = banner_counters[banner]
current_cond = req['condition1']
if current_cond != counter['last_cond']:
counter['dim_idx'] += 1
counter['value_idx'] = 1
counter['last_cond'] = current_cond
else:
counter['value_idx'] += 1
banner_idx = re.search(r'\d+', banner).group()
req['new_var_name'] = f"banner{banner_idx}_{counter['dim_idx']}_{counter['value_idx']}"
req['code'] = counter['value_idx']
req['label'] = req['condition2'] if req['condition2'] else req['condition1']
# 生成最终版Excel
wb_out = Workbook()
ws_out = wb_out.active
ws_out.title = "最终版"
headers = ['需求信息', '条件1', '条件2', '条件3', '需求定义',
'变量名称', '变量标签', '数据逻辑定义', '新变量名', '标签', 'code']
for col, header in enumerate(headers, 1):
cell = ws_out.cell(1, col, header)
cell.font = Font(bold=True, color='FFFFFF')
cell.fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
for row_idx, req in enumerate(requirements, 2):
ws_out.cell(row_idx, 1, req['demand_info'])
ws_out.cell(row_idx, 2, req['condition1'])
ws_out.cell(row_idx, 3, req['condition2'])
ws_out.cell(row_idx, 4, req['condition3'])
ws_out.cell(row_idx, 5, req['demand_def'])
ws_out.cell(row_idx, 6, req['matched_var'])
ws_out.cell(row_idx, 7, req['var_label'])
ws_out.cell(row_idx, 8, req['logic_def'])
ws_out.cell(row_idx, 9, req['new_var_name'])
ws_out.cell(row_idx, 10, req['label'])
ws_out.cell(row_idx, 11, req['code'])
wb_out.save(output_file)
完整处理流程代码
# 主函数
def main():
input_file = '输入文件.xlsx'
output_prefix = '维度定义表'
print("=" * 60)
print("dimension-definer-pro 维度定义处理")
print("=" * 60)
# Step 1: 格式识别与解析
print("\n[Step 1] 格式识别与解析...")
banner_data, var_data, value_data = step1_parse_files(input_file)
# Step 2: 数据清洗与标准化
print("\n[Step 2] 数据清洗与标准化...")
banner_data, var_data, value_data = step2_clean_data(banner_data, var_data, value_data)
# Step 3: 数据整理
print("\n[Step 3] 数据整理...")
requirements = step3_organize_data(banner_data)
# Step 4: 维度匹配与定义生成
print("\n[Step 4] 维度匹配与定义生成...")
requirements = step4_match_dimensions(requirements, var_data, value_data)
# Step 5: 质量检查与报告
print("\n[Step 5] 质量检查与报告生成...")
completeness = check_completeness(requirements, var_data, value_data)
verification = verify_sources(requirements, var_data, value_data)
match_rate = calculate_match_rate(requirements)
# 5.4 输出审核文件(3个Sheet)
print(" 生成审核文件(含待审核版+变量表+值标签表)...")
generate_review_file(requirements, var_data, value_data,
f"{output_prefix}-审核版.xlsx")
# 生成报告
report = generate_report(requirements, completeness, verification, match_rate)
with open(f"{output_prefix}-处理报告.md", 'w', encoding='utf-8') as f:
f.write(report)
print("\n" + "=" * 60)
print(f"第一轮处理完成!")
print(f" 审核文件: {output_prefix}-审核版.xlsx")
print(f" 处理报告: {output_prefix}-处理报告.md")
print(f" 整体匹配率: {match_rate['match_rate']}%")
print("=" * 60)
print("\n请审核后发送反馈文件,我将生成最终版。")
# 第二轮:基于审核反馈生成最终版
def main_final(review_file, output_prefix):
"""基于审核反馈生成最终版"""
print("=" * 60)
print("dimension-definer-pro 生成最终版")
print("=" * 60)
print(f"\n读取审核文件: {review_file}")
generate_final_from_review(review_file, f"{output_prefix}-最终版.xlsx")
print("\n" + "=" * 60)
print(f"最终版生成完成!")
print(f" 最终版: {output_prefix}-最终版.xlsx")
print("=" * 60)
输入文件要求
必需的工作表
| Sheet名称 | 说明 | 必需 |
|---|---|---|
| Banner | 表头需求定义 | 是 |
| 变量 | SPSS变量表 | 是 |
| 值标签 | SPSS值标签表 | 是 |
Banner表结构
行1: Banner名称(如"Banner1-人口学")
行2: 空行
行3: Total | 维度类型1 | 维度类型2 | ...
行4: 空行
行5: 维度值1 | 维度值2 | ...(同时也是Definition)
行6: Definition1 | Definition2 | ...
变量表结构(SPSS格式)
│名称 │位置│标签 │...
├────────────────────┼────┼────────────────────────────────────────────────────┤
│序号 │ 1│序号 │...
│VAR_A │ 2│完成状态 │...
│D2 │ 15│D2_您的性别是 │...
值标签表结构(SPSS格式)
│Variable │Value│Label │...
├────────────────────┼─────┼────────────────────────────────────────────────────┤
│D2 │ 1│男性 │...
│ │ 2│女性 │...
输出文件格式
待审核版
| 列名 | 说明 |
|---|---|
| 需求信息 | Banner-维度类型-维度值 |
| 条件1-3 | 拆分后的条件 |
| 需求定义 | 业务定义 |
| 变量名称 | 自动匹配的变量名 |
| 变量标签 | 变量描述 |
| 数据逻辑定义 | 初步逻辑定义 |
| 匹配状态 | 自动匹配/模糊匹配/需人工审核 |
| 置信度 | 匹配置信度 |
最终版
| 列名 | 说明 |
|---|---|
| 需求信息 | Banner-维度类型-维度值 |
| 条件1-3 | 拆分后的条件 |
| 需求定义 | 业务定义 |
| 变量名称 | 审核后的变量名 |
| 变量标签 | 变量描述 |
| 数据逻辑定义 | 完整逻辑定义 |
| 新变量名 | banner{序号}{维度序号}{值序号} |
| 标签 | 维度值标签 |
| code | 每个维度内从1递增 |
更新日志
v2.1.3 (2026-03-30)
值标签解析完整性修复 - 关键修复
问题8: 值标签表解析不完整(关键修复)
- 根因1:正则表达式
(\d+\.?\d*)无法匹配.00格式(如.00 否) - 根因2:依赖分隔线(├─)判断变量分组,但文件中很多变量连续排列无分隔线
- 解决1:改进正则为
((?:\d+)?\.\d+),支持可选数字前缀的小数 - 解决2:通过变量名存在性判断新变量开始,不依赖分隔线
- 效果:变量数量从 96个 → 692个(+620%,完整提取所有变量)
- 示例:
- D1_A1变量:
.00 否 | 1.00 是(之前完全遗漏)✓ - 触点变量:48个值标签(之前只提取4个)✓
- D1_A1变量:
- 函数:
parse_value_sheet_fixed()v2.1.3修复版
v2.1.0 (2026-03-27)
重大修复版本 - 修复3个核心问题
问题1: Banner解析不完整(关键修复)
- 根因:错误理解Total行结构,将Total行的下一行当成维度类型行
- 解决:Total行(行26)同时是维度类型行,添加前向填充逻辑
- 效果:从部分Banner输出 → 完整13个Banner(195条→138条正确解析)
- 更新函数:
parse_banner_sheet_v3()
问题2: 需求信息格式错误(关键修复)
- 根因:混淆维度类型和维度值,导致"酒店-携程-酒店-携程"错误格式
- 解决:正确解析三行结构(行26类型/行27值/行28Definition)
- 效果:正确格式"Banner5-平台服务体验-经服业务线-携程-酒店-携程"
问题3: 变量标签截断(关键修复)
- 根因:代码中有
[:100]截断标签 - 解决:移除截断,输出完整标签(支持200+字符)
- 更新函数:
generate_review_output()
问题4: 值标签表输出不完整
- 根因:code行变量名为空时未继承上一个变量名
- 解决:使用
current_var变量跟踪确保code正确关联 - 效果:从3个变量→570个变量,1349个code全部正确解析
- 函数:
parse_value_sheet_fixed()
问题5: 匹配率低(重要改进)
- 根因:只支持简单变量匹配
- 解决:新增多种匹配策略
- 子变量匹配(D6=1→D6_1)
- 复合维度值匹配("酒店-携程"→D7_1_1)
- 三级题号匹配(D7_A=1→D7_1_1)
- 语义一致性验证
- 效果:匹配率从27.54%提升到78.26%(+50.72%)
- 函数:
step4_match_dimensions()
问题6: 多级嵌套题号匹配(v2.1.1新增)
- 根因:无法匹配 D8_A=1 → D8_SQ1_1 这种多级嵌套结构
- 解决:新增基于多维度信息交叉验证的通用匹配方法
- 提取题号前缀(D8)
- 提取一级分类(在线客服/电话客服)
- 从维度值提取平台和二级分类(携程-在线 → 平台=携程, 分类=在线)
- 多维度交叉验证:题号前缀 + 平台名 + 分类关键词 + code
- 效果:支持 Banner7 等复杂嵌套结构匹配
- 示例:
- D8_A=1 + 在线客服 + 携程-在线 → D8_SQ1_1 ✓
- D8_B=1 + 在线客服 + 去哪儿-在线 → D8_SQ2_1 ✓
- D8_A=2 + 电话客服 + 携程-电话 → D8_SQ1_2 ✓
- 函数:
step4_match_dimensions()新增第二级通用匹配
问题7: 数据逻辑定义输出错误(v2.1.2新增)
- 根因:数据逻辑定义直接输出原始definition,未转换为中文标签
- 解决:新增
generate_logic_definition()函数- 从值标签表查找code对应的中文标签
- 支持
/转换为"或",,转换为"且" - 支持多选题输出(如 D7_5_3=是)
- 效果:
- D2=1 → D2=男性 ✓
- D3a=3/4 → D3a=25-29岁或D3a=30-34岁 ✓
- D7_E=3 → D7_5_3=是 ✓
- 函数:
generate_logic_definition()新增
v2.0.0 (2026-03-27)
- 重构为5步标准化处理流程
- 添加完整的Step 1-5详细说明
- 支持混合结构Banner表解析
- 优化变量名提取逻辑(从Definition)
- 完善质量检查和报告生成
- 添加完整的代码示例
v1.3.0 (2026-03-26)
- 添加人工审核流程规范
- 明确两轮处理机制(review + final)
- 完善输出格式规范
v1.0.0 (2026-03-26)
- 初始版本