← Back to skills
extension
Category: Data & AnalyticsNo API key required

人群标签洞察

上通五(上汽通用五菱)车型与细分市场人群标签智能问数。数据为「人群包编号 × 画像维度切片 × 标签实例 × 命中人数(加权小数)」宽表, 用户以自然语言提问某车型 / 某条件下某标签的百分比分布、某人群包的画像构成(性别 / 年龄段 / 城市等级 / 是否多人口), 也会问排名类三表(车机流量 / APP 使用排名 / 商圈到访)、单车型完整洞察报告、多人群包画像差异对比。 本技能是该系列工作的最终权威口径版:输出仅百分比,全程绝不吐露命中人数绝对值,含柱状图可视化与百分比 label (⛔ 商圈到访例外:无数值字段,一律明细表格展示,不画柱状图 / 色条), 支持空态 / 缺维 / 小样本三类友好提示。另支持第二数据源「火山完整数据_202608.xlsx」(已算好占比的结果表,只直接展示、不计算)。 数据源默认 洞察数据_full.csv(GBK 编码,约 435 万行),由 resolve_data.py 定位,不写死路径。 另支持用户问「现在都有哪些数据标签 / 库里有哪些标签 / 都能查哪些维度」时,把库内现有标签 + 排行榜去重、分类后直接在对话内展示(不生成 HTML)。

personAuthor: user_ccefd952hubcommunity

上通五人群标签智能问数 (shangtongwurenqun-wenshu)

何时使用

  • 用户在人群洞察项目目录下提问,按意图分三主线(精确路由见《场景路由》):
    • 场景一(即时查询):"某车型(R5 / R8 / R24-1 等)的经济水平分布""某条件下某标签占比""R5 的性别 / 年龄段 / 城市等级是怎么分布的""标签交叉分布" → 对话内直接给百分比表 + 柱状图,不生成 HTML。
    • 场景二(单包全量):"R48 画像情况怎么样 / 给我 R48 的完整洞察" → 输出 <pack>_画像总览_全标签.html。
    • 场景三(多包对比):"R64 和 R48 / 多个包的画像差异 / 多包标签对比" → 输出 <A>_vs_<B>[_vs_...]_画像差异_全标签.html。
  • 另有排名类三表(车机流量 / APP 使用排名 / 商圈到访)属结果表直接展示,不参与标签对比(见《排名类三表》);开发 / 调本问数技能时也适用本技能。
  • 库内标签 / 排行榜速查(场景零):用户问"现在都有哪些数据标签""库里有哪些标签""都能查哪些维度""有哪些排行榜" → 不进三主线,直接跑 scripts/_list_label_catalog.py 把去重分类后的目录贴进对话(见《标签目录总览》)。
  • 数据形态:每行 = 人群包 × 画像维度组合 × 标签实例;问题本质是「百分比分布 / 构成 / 交叉」。是则用本技能。
  • 判断方法:只要看到"命中人数"这类加权小数指标 + 人群包编号 + 标签名称 / 标签值 的宽表,且问的是占比,就走本技能。

数据源路由(先看这一节,选错源 = 全盘返工)

| 数据源 | 文件 | 形态 | 何时用 | 处理方式 | |---|---|---|---|---| | 洞察数据(主源) | 洞察数据_full.csv | 明细宽表,需计算 | 问标签档位分布、画像构成、带画像维度筛选的问题 | 按本文口径 A / B 透视求占比 | | 火山数据(第二源) | 火山完整数据_202608.xlsx | 已算好占比的结果表,无画像列 | 问职业 / 行业 / 手机品牌 / 消费能力等火山专属标签,或用户明说"火山" | 直接展示,不做任何计算(见《第二数据源》节) | | 排名类三表(第三源) | 车机流量使用_8.xlsx APP使用排名_9.xlsx 商圈排名_10.csv | 结果表 | 问某包的车机流量使用 / APP 使用排名 / 商圈到访排名 | 直接展示,不归一、不解释(见《排名类三表》) |

  • 三者形态完全不同,勿互相套口径:洞察 CSV 有画像维度和命中人数、需要透视;火山是 69 人群包 × 64 标签的现成占比表;排名类三表是排行榜结果表。
  • 精确结构见 references/real-data-profile.md、references/volcano-data-source.md、references/ranking-data-sources.md。

场景路由(问法意图 → 走哪条路)—— 主线三场景

判定顺序:先看「问的是几个人群包 + 要的是单个标签还是整体画像」,再决定走哪条主线;火山 / 排名类只是叠加在主线上的数据源变体(形态不同,勿互相套口径)。

注:下文详细实现分 场景2(单包全量报告) 与 场景3(多包对比报告) 两节,即本表的「场景二 / 场景三」;「场景一」为对话即时查询,方法论见《透视口径 A / B》与《输出模板》,不单独成节。

| 主线意图 | 场景 | 触发问法(典型) | 产出形态 | |---|---|---|---| | ① 单包 + 某标签分布(可带维度交叉) | 场景一 | "R8 的经济水平分布""R5 + 新一线 + 90后 → 话费区间""R5 的性别 / 年龄 / 城市 / 是否多人口 分布" | 对话即时回复:百分比表 + 柱状图(带 % label),不生成 HTML | | ② 单包整体画像 / 全部标签分布 | 场景二 | "R48 画像情况怎么样""给我 R48 的完整洞察" | 输出 HTML:<pack>_画像总览_全标签.html(10 一级 TAB) | | ③ 两个或三个及以上人群包对比 | 场景三 | "R64 和 R48 的画像差异""多个包的标签对比" | 输出 HTML:<A>_vs_<B>[_vs_...]_画像差异_全标签.html(7 一级 TAB,N 包合并单文件) | | ④ 问"库里现在都有哪些标签 / 排行榜" | 场景零 | "现在都有哪些数据标签""库里有哪些标签""都能查哪些维度""有哪些排行榜" | 对话内直接展示:跑 _list_label_catalog.py 输出去重分类目录(Markdown),不生成 HTML |

场景一:对话即时查询(单标签 / 画像构成 → 表 + 柱状图)

  • 定位:最高频问法,只问「一个人群包下某一个标签(或某一画像维度)的分布」,要即时回答,不做成报告文件。
  • 两种口径(看问句宾语,详《透视口径 A》《透视口径 B》):
    • 口径 A(标签档位分布):宾语是「标签名称」(经济水平 / 话费区间 / 换机价位 …),可叠加任意画像维度筛选(性别 / 年龄段 / 城市等级 / 是否多人口)。如 "R8 的经济水平分布""R5 + 新一线 + 90后 → 话费区间"。
    • 口径 B(画像构成分布):宾语是「画像列本身」(性别 / 年龄段 / 城市等级 / 是否多人口),经济水平只做取数快照锚点。如 "R5 的性别 / 年龄 / 城市 分布"。
  • 火山单标签变体:若问火山专属标签(职业 / 行业 / 手机品牌 / 消费能力等),切到火山源直接展示「取值 ↔ 占比」两列,不计算、不归一、不画柱状图(火山是已算好占比的结果表,与洞察口径 A/B 不同源,见《第二数据源》)。
  • 交付形态:先百分比表 → 再一句数据驱动结论 → 配柱状图(横轴档位、纵轴 %、每柱带 % label)。⛔ 商圈到访例外:无数值字段,不画图,改明细表格(见《排名类三表》)。

场景二 / 场景三:HTML 报告(单包全量 / 多包对比)

  • 场景二(单包全量):触发 = 用户要一个人群包的全部标签分布 / 整体画像,而非单一标签。产出 = <pack>_画像总览_全标签.html(10 一级 TAB)。详《场景2:单车型完整标签洞察报告》。
  • 场景三(多包对比):触发 = 用户提到两个(≥2)或三个及以上人群包,要对比画像 / 标签差异。产出 = <A>_vs_<B>[_vs_...]_画像差异_全标签.html(7 一级 TAB,所有包合并进【同一份】HTML)。详《场景3:多人群包画像差异对比》。

数据源叠加规则:三场景默认走洞察数据(主源);问火山专属标签 → 切火山源(直接展示);问车机流量 / APP 使用排名 / 商圈到访 → 切排名类三表(结果表直接展示,不参与标签对比)。详见《数据源路由》。

标签目录总览(场景零:库内标签 / 排行榜速查)

用户问"现在都有哪些数据标签""库里有哪些标签""都能查哪些维度""有哪些排行榜"等盘点类问题时,不进三主线、不生成 HTML、不算占比,直接把库内现有「标签 + 排行榜」去重、分类后贴在对话里。

判定

  • 问句的宾语是「标签全集 / 维度全集 / 排行榜全集」这类盘点意图(要一份"清单"),而非某个具体包某个标签的分布 → 走本场景。
  • 若顺带指定了具体人群包(如"R48 有哪些标签")→ 仍走本场景,但只列该包的标签(脚本暂按全库口径;指定包时由 run_query --list-tags 探后算,见 Pitfall 9)。
  • 与三主线的区别:三主线是"某包某标签的百分比分布";本场景是"库里到底有哪些标签 / 排行榜可以查"。

执行(确定性脚本优先)

python scripts/_list_label_catalog.py [--data-root "<数据目录>"] [--json]
# 默认打印一份 Markdown 分类目录,直接贴进对话回复
  • 脚本只做去重 + 分类,不读 命中人数_总和、不算占比 → 铁律 0 天然满足(全程不接触绝对值)。
  • 分类口径:
    • 洞察数据:按 标签大类 → 人群标签 / 业态到访 / 驾驶行为(各列出去重后的 标签名称)。
    • 火山数据:按 标签分类 → 人群标签 / 内容偏好 / 汽车内容关注 / 消费行为(Sheet2,各列出去重后的 标签名称)。
    • 排行榜:车机流量使用 / APP 使用排名 / 商圈到访 三类单列(结果表,不参与标签对比)。
  • 额外给出:人群包去重总数(元数据,非命中人数)、各分类标签计数。

对话交付形态(直接展示,不生成文件)

  • 把脚本输出的 Markdown(## 库内数据标签 / 排行榜总览 … 三段式)原样贴在对话里即可。
  • 不补口径解释、不补百分比、不提命中量;末尾自动附一句"两源标签名无交集、报告中人群标签并列合并"的说明即可。
  • 若用户还想看某个标签的具体分布,再路由回场景一(口径 A / B)。

铁律(用户反复强调,违反即返工)

🔴 铁律 0 — 任何环节绝不吐露命中人数绝对值(最严重,零容忍)

  • 交付表格、口头解释、中间说明、小样本注解,一律不得出现绝对数字(如 0.588 / 8.4 / 661 这类)。命中人数只是藏在代码里算占比的内部变量,永远不进给用户的回复;它仅可作自检锚点,只能"在心里 / 在代码 print 里核对",绝不能落进任何给用户的文字(连口头带一句都不行)。
  • 小样本处理:直接给 %,值不稳定 / 档位塌缩时附一句"样本过少,结果仅供参考"即可,**不要解释"合计多少 / 还剩几行 / 只剩两档"**等任何带绝对量的措辞。
  • 只有遇到"该筛选条件在数据里查无 / 无法算"时,才说"数据缺少筛选条件不能算",并说明缺的是哪个条件,不绕圈。

🔴 铁律 0b — 交付直接给结果,不展露思考过程

  • 不需要(也不喜欢)看到铺开的中间思考链 / 诊断史。命中就给"先百分比表格 → 再一句必要警示";不要"我发现 X 我觉得 Y 所以我先 Z"的长铺垫。

🔴 铁律 0c — 交付只给「百分比表 + 数据本身能支撑的一句结论」

  • ✅ 只含干净的百分比分布表 + 数据自己直接显示出来的一句结构性结论(如"男性为主、占八成""主力在 85-90 后")。
  • ❌ 不给带量化推断 / 旁白的解读——如"女性占比比整体高 X 个百分点""样本量较少注意"这类拿命中数去比或用暗示量级的措辞,全部禁止。
  • ❌ 不在表后"补充口径解释""我如何筛选"等多余说明,除非用户主动问口径。
  • ✅ 若要提示小样本 / 仅供参考,只能用不含任何数量的话("样本过少,结果仅供参考");其余画面留给百分比表。

其余铁律

  1. 权威口径先于猜测:先读 说明.txt / 说明.docx,其"常见提问"段是权威透视口径,不要凭直觉猜透视逻辑。
  2. 画像构成分布 → 先剔除「性别无效行」再分组:问某人群包"性别 / 年龄 / 城市 / 是否多人口 分布"时,性别为 空值 / NaN / null / 未知 的行整行剔除(不参与计算、不展示,分母随之减小),其余按目标画像列分组归一。
    • 4 个维度一视同仁:性别、年龄段、城市等级、是否多人口 全部先做这一步("未知"与空值是性别列的孤儿值,会污染按其他维度的构成)。
    • 无例外:即便问的就是"性别分布",未知 / 空值档位也不展示,直接忽略(用户 2026-09-10 裁定)。
    • 各维度自身的空值 / NaN 档位同样不展示(不允许出现空白标签的柱子)。
    • 实现:_profile_report_v2.load_demographics() 与 run_query.compute_profile() 已内建;无效值集合 {'', 'nan', 'none', 'nat', 'null'},性别额外加 {'未知','unknown'}。
  3. 可视化默认柱状图:横轴 = 档位、纵轴 = 百分比(带 % label)。可再给饼图 / 折线图。HTML 报告里低占比档位必须用"档内相对条"(条宽 = 本档 ÷ 本标签最大档),数值仍显示真实值。
    • ⛔ 唯一例外 = 商圈到访(用户 2026-09-14 裁定):商圈数据只有「排名等级 + 商圈名称」两个文本字段、没有任何数值,画条只能是假的 100%,毫无信息量。一律用明细表格展示,不画柱状图 / 色条——报告里末层用 .mtable 表格,对话里直接给 Markdown 明细表。此例外覆盖本条默认规则。
  4. 三类友好提示,措辞区分:
    • 空态(所选画像条件组合查无切片 / 0 行):提示"该筛选条件下没有满足的数据(请放宽或减少筛选维度)",输出空。
    • 缺维(数据文件 / 整列压根没有某画像字段):先提示"该数据缺少 X 维度",仍展示"忽略该维度"后的分布。
    • 样本过少(非 0 行但命中合计极小、档位塌缩,如 R9 / R9+ 一线):给 % 并附"样本过少,结果仅供参考"一句即止,绝不解释绝对量。
  5. canonical 校准:用 说明.txt 的 4 个 canonical 问题先验证口径跑通(见 references/schema-and-canonical-queries.md),对齐后再全量;换环境必跑 verify_data.py。

数据结构(权威 · 洞察数据_full.csv 实测版)

| 中文名 | 字段名 | 取值 / 备注 | |---|---|---| | 人群包编号 | 人群包编号 | R 开头 = 具体车型(R5 / R8 / R9 / R24-1);其余 = 细分市场编码(如 A0-SUV-纯电动、A级别-轿车-混动)。本数据没有 R6(只有 R60–R69) | | 性别 | 性别 | 女 / 男 / 未知 / 空值 / NaN。⚠️ 空值 / NaN / null / 未知 一律不参与计算也不展示(铁律 2、Q13) | | 年龄段 | 年龄段 | 00后 ~ 60前,约 11 档 | | 动力类型 | 动力类型 | 纯电动 / 汽油 / 插电式混合动力 / 增程式 / 油电混合(含脏值,慎做精确匹配) | | 标签大类 | 标签大类 | 人群标签 / 驾驶行为 / 业态到访 | | 标签名称 | 标签名称 | 人群偏好车、车主类型、人生阶段、话费区间、换机价位、经济水平、驾驶时间段等 | | 标签值 | 标签值 | 该标签档位(如经济水平 高 / 中高 / 中 / 中低 / 低)。直接统计,不必预置档位枚举 | | 城市等级 | 城市等级 | 实测全量均为带空格写法 Tier 1 / Tier 1 New / Tier 2~Tier 5。口语映射:Tier 1 = 一线、Tier 1 New = 新一线。筛前务必 .str.replace(' ','',regex=False) 归一化再比 Tier1 | | 经济水平 | 经济水平 | 唯一有三种身份的字段:① 目标标签 ② 画像维度筛选 ③ 人群覆盖快照锚点(见下) | | 是否多人口家庭 | 是否多人口家庭 | 是 / 否(字符串,非 1 / 0) | | 命中人数_总和 | 命中人数_总和 | 唯一数值列,加权小数,且带千分位逗号的字符串(先 str.replace(',','') 再 to_numeric)。某人群包某标签全档求和常回整数,可作一致性自检锚点 |

⚠️ 合并 csv 实际列名与原始 xlsx 描述有差异:指标列是 命中人数_总和(无单独的「命中人数」列),家庭列叫 是否多人口家庭,标签体系列叫 标签大类。详见 references/real-data-profile.md。

透视口径 A — 标签档位分布(canonical)

一次问数 = 「画像维度筛选」+「锁定目标标签」+「各标签值档位百分比」。

  • 分子 = 目标标签在某档位(标签值)下、筛选后所有行的 命中人数_总和 求和。
  • 分母 = 同筛选下该目标标签所有档位的 命中人数_总和 求和。
  • 档 % = 分子 / 分母 × 100,各档和 ≈ 100%。
  • 问题形态四级(精确步骤见 references/schema-and-canonical-queries.md):
    1. 单包单标签(R8 经济水平)
    2. 多画像条件筛单标签(R5 + 新一线 + 90后 + 女 + 经济高 → 话费区间)
    3. 多包整体合并(R5 + R6 合并为一个整体)
    4. 多包分组各自归一(R5 / R6 各出一份分布)

透视口径 B — 画像构成分布(经济水平做锚点)★ 用户场景更新后敲定

当问句宾语是画像列本身("R5 的性别 / 年龄段 / 城市等级 / 是否多人口 分布""R5 的人群画像构成")时,走本口径:输出的不是经济水平 5 档表,而是目标画像列各值的构成占比。

  1. 锚点:把 经济水平 标签行当作该人群包的完整人群覆盖快照(经济水平全 5 档齐、覆盖所有画像切片、数据正交)。
  2. 取数:sub = df[(df['人群包编号'].str.strip() == R#) & (df['标签名称'] == '经济水平')]。
  3. 剔除性别无效行:空值 / NaN / null / 未知 的性别行整行剔除,不参与计算也不展示(分母随之减小)。4 个维度都要先做这一步(场景2 基础属性 TAB 的 load_demographics() 已内建)。
  4. 分组:按目标画像列(性别 / 年龄段 / 城市等级 / 是否多人口家庭)分组,命中人数_总和 求和 = 分子;分母 = 该列所有值的命中和(= 快照人群总量)。
  5. 占比 % = 分子 / 分母 × 100。经验证:各画像列独立求和互相相等、且等于经济水平全档和 → 任意单列分组占比天然归一到 100%,可直接交付。
  6. 交付:每维一张百分比表(先 % 表 → 再一句数据驱动的结论),可配柱状图。绝不吐露命中绝对值。

口径 A / B 判定铁律:看问句的宾语。

  • 宾语 = "经济水平" → 经济水平当目标标签,出 高 / 中高 / 中 / 中低 / 低 5 档(口径 A)。
  • 宾语 = "性别 / 年龄 / 城市 / 多人口"等画像字段 → 经济水平只做取数快照锚点,按该画像列输出构成(口径 B)。
  • 第三种身份(快照锚点)≠ 双身份(当标签 vs 当扣减画像筛选),勿混为一谈。
  • R5 口径 B 回归锚点见 references/real-data-profile.md。

自然语言 → 参数 翻译裁决(Q1–Q14 敲定,勿再臆测)

  • 标签档位:不预知档位枚举,直接用数据的 标签值 统计 / 展示。
  • 画像维度:数据里有什么就筛什么,用原始枚举即可(城市口语映射例外:Tier 1 = 一线 / Tier 1 New = 新一线)。
  • 人群包模糊包含:"A0 级别 SUV" → 筛 人群包编号 含子串 "A0-SUV";具体 R 车型直接精确(注意 strip)。多包("R5 和 R6")→ 进 合并 / 分组 形态,且必须回报"数据中不存在的人群包"。
  • 双身份规则(仅经济水平):全字段里只有 经济水平 能同时当"目标标签"和"画像维度筛选";其余画像字段(性别等)不能当标签。判定靠语法位置:想看"X 分布"→ X 当标签;"X 为…的 Y 分布"→ X 当画像筛选、Y 当标签。其它标签名(换机 / 话费 / 房价 / 尝鲜)无对应画像列,不能当筛选。
  • 多标签同问("R5 的经济水平和话费区间分布"):每个标签各自出一份分布,不做成单表夹一起。
  • 命中人数绝对值仅用于内部求占比,交付不显(Q8 / 铁律 0)。

第二数据源:火山完整数据_202608.xlsx(直接展示,不计算)

何时用

用户问"火山…"或问某个不依赖画像维度的标签(职业 / 行业 / 手机品牌 / 消费能力等)且该标签属于火山标签库 → 用本文件。不要套洞察 CSV 的透视 / 画像口径。

关键事实(详见 references/volcano-data-source.md)

  • 文件:火山完整数据_202608.xlsx,sheet = 'Sheet2'(不是 Sheet1),约 6.08 万行,时间周期只有 202608。
  • 列:人群包, 标签名称, 标签取值, 标签占比, 标签TGI, 时间周期, 品牌分类, 价格档位, cat_1, cat_2。标签占比 已算好(0.4391 = 43.91%)。⚠️ 场景2 脚本里读的火山占比列名是 标签值占比(不是 标签占比)。
  • 69 个人群包(全 R 开头)× 64 个标签;cat_1/cat_2 的空值是导出冗余,不参与问数。
  • 无画像维度列 → 只支持「人群包 × 标签」一个向度:sub = df[(df['人群包']==R#) & (df['标签名称']==<准确标签名>)] → 列出 标签取值 + 标签占比×100。
  • ⚠️ 用户铁律(针对此源):直接展示 取值 + 占比两列,不做任何计算、不做归一、不补缺档、不做任何解释。各档占比通常和 ≠ 100%(例 R1 预测-职业 7 档和 ≈ 70.33%),不要归一、不要提示"只列了部分"、不要解释为什么不齐,原样列出即可。
  • 标签名是规范全名且可能带全角括号 / 后缀(如"预测 - 职业(新)")。用户口语"预测-职业"→ 模糊匹配数据里的实际标签名,匹配到唯一后用它,别拿口语当精确键。

⭐ 火山"占比和 ≠ 1"的真相(勿误修)

全量 4812 个「包 × 标签」组合实测:不是 bug,是三类标签的正常语义,强行归一反而失真。

| 类型 | 典型标签 | 档内和 | 语义 | 正确处理 | |---|---|---|---|---| | 多选型 | 意向关键词(2.43)、视频/资讯一级兴趣分类(2.0)、全品牌车系偏好(峰值9.89) | >1 | 一人可命中多取值 | 保留原样,不归一 | | 单选型 | 学历(0.936)、手机品牌(0.936)、设备价格(0.930) | ≈0.93 | 剩余未导出档(折叠为"其他") | 可选补"其他=1−和",或原样 | | 条件/门槛型 | 汽车用车场景(0.003)、二手车购买意向(0.006)、线上内容兴趣-资讯-旅游(0.022) | ≪1 | 分母 = 全人群 | 保留原样,归一会把 0.3% 放大成 100% 严重失真 |

结论:绝不做全局归一化。 用户裁定的是修图表观感(相对条),数值保真不动。

排名类三表(车机流量 / APP 使用排名 / 商圈到访)— 直接展示,不计算

何时用

问某人群包的车机流量使用情况 / APP 使用排名 / 某城市商圈到访排名 → 走本场景。直接展示数据,不做任何归一 / 计算 / 解释(与火山源同性质:结果表)。

三表结构(实测,详见 references/ranking-data-sources.md)

| 文件 | 列 | 说明 | |---|---|---| | 车机流量使用_8.xlsx | 人群包编号, 二级类型, 月活, 活跃天数, 用量, 使用时长, month_id | 108 人群包 × ~92 二级类型;各包条数因包而异(R48=65 / R64=80 / R4=87),勿硬编码 | | APP使用排名_9.xlsx | 人群包编号, 排名方式, 排名序号, app名称, 占比, month_id | 5 种排名方式,每种 TOP1~TOP15 | | 商圈排名_10.csv | 人群包编号, 城市, 商圈名称, 排名等级, month_id | utf-8;106 人群包 × 339 城市;Top1~Top10 |

三种问法口径

  1. 车机流量使用 → 直接取该包 二级类型 × 月活/活跃天数/用量/使用时长,原样展示(不归一、不合列)。
  2. APP 使用排名 → 未指定方式则按 5 种方式分开展示(APP使用人数排名 / 人均使用天数 / 人均使用时长 / 人均使用次数 / 人均使用流量);指定方式只出该张。排序用 排名序号 里的数字(str.extract(r'(\d+)') 转 int),不能按字符串排(否则 TOP10 排到 TOP2 前)。⚠️「占比」列量纲随方式变化:人数排名 ≈ 0~1 比例,人均流量 / 时长是绝对值(如 1356.0),原样展示不换算。
  3. 商圈到访 → 指定城市展示该城 Top1-10;未指定则展示该包全部城市。城市名须精确匹配数据写法(口语"北京" → 数据里是"北京市"),先用 mall_cities(pack) 查候选再筛(R48 有 271 城)。
    • ⛔ 展示形式 = 明细表格,不画柱状图 / 不画色条(用户 2026-09-14 裁定)。该源只有 城市 / 商圈名称 / 排名等级 三个文本列,没有数值,任何条形图都是假的 100%。
    • 对话输出 = Markdown 明细表:指定城市 → | 排名 | 商圈名称 |;未指定城市 → | 城市 | 排名 | 商圈名称 |(全部城市,行数很大时按 Top10 原样列出,不做裁剪 / 不解释)。

铁律仍适用:三表本身是结果表,里外都不得出现任何命中人数绝对值;"占比"列是文件自带字段可原样展示,但绝不可附加任何自算的绝对量。

已验证实例(R48 实测,可对答案)

  • 车机流量 R48:65 个二级类型。TOP 场景 = 地图导航(使用时长 4566.98、活跃天数 21.17)、行车辅助(月活 0.7849、用量 696.15、使用时长 3038.51)、综合电商(月活 0.7741)、在线音乐(月活 0.6381、用量 440.52)。
  • APP 使用人数排名 R48 TOP10:微信 68.33% / 高德地图 65.95% / 手机淘宝 65.12% / 腾讯地图 64.16% / 百度地图 63.62% / 今日头条 63.59% / 淘宝 63.42% / 腾讯视频 62.76% / 支付宝 62.45% / 优酷视频 60.94%。
  • 商圈 R48 北京市 TOP10:北京荟聚 / 朝阳大悦城 / 世纪金源购物中心 / 贵友大厦(金源店) / 世贸天阶 / 国贸商城 / 世贸天阶北街 / 绿地缤纷城(大兴店) / SOLANA蓝色港湾 / 世纪金源购物中心西区。

场景2:单车型「完整标签洞察」报告(「R48 画像情况怎么样」)

一个 <pack> → 一份自包含 HTML 报告,涵盖该车型全部可获取的洞察数据源,分 TAB 平铺。

python scripts/_profile_report_v2.py R48 --data-root "<数据目录>" --out "<输出目录>"
# → <输出目录>/R48_画像总览_全标签.html

报告结构:10 个一级 TAB(R48 实测)= 基础属性 + 6 标签大类 + 3 排名类

| # | 一级 TAB | 来源 | 二级交互 | 计数 | |---|---|---|---|---| | 0 | 基础属性 | 洞察 CSV(经济水平锚点快照) | 4 维 chip(性别/年龄段/城市等级/是否多人口) | 4 | | 1 | 人群标签 | 洞察 13 + 火山 10 合并同名分类 | 标签 chip | 23 | | 2 | 业态到访 | 洞察 | 标签 chip | 21 | | 3 | 驾驶行为 | 洞察 | 标签 chip | 14 | | 4 | 内容偏好 | 火山 | 标签 chip | 25 | | 5 | 汽车内容关注 | 火山 | 标签 chip | 17 | | 6 | 消费行为 | 火山 | 标签 chip | 12 | | 7 | 车机流量使用 | 车机流量_8 | 4 指标 chip(月活/活跃天数/用量/使用时长),各块内独立降序 | 因包而异(R48=65) | | 8 | APP 使用排名 | APP 排名_9 | 5 排名方式 chip,每方式 TOP1~15 | 5×15 | | 9 | 商圈到访 | 商圈排名_10 | 三层平铺下钻(省 → 城市 → Top1-10 商圈) | R48=30 省 |

⭐ 基础属性 TAB(置顶,4 维)

  • 4 个维度:性别 / 年龄段 / 城市等级 / 是否多人口(= 是否多人口家庭,显示名简写)。
  • 取数锚点:以 标签名称==经济水平 的行为该包完整人群覆盖快照(数据正交),按目标画像列分组求和 ÷ 快照命中和 —— 即 口径 B。
  • ⚠️ 性别无效行排除:性别 = 空值 / NaN / null / 未知 的行整行 drop;4 个维度统一生效(避免孤儿值污染)。各维度自身的空值 / NaN 档位也不展示。
  • 实现:load_demographics(pack) → {显示名: {'档位':{值:pct}, 'order':[...]}},复用 _bars() 渲染。
  • 实测 R4(剔除性别无效行后):性别 男 70.75% / 女 29.25% · 年龄段 85后 17.86% / 90后 17.45% / 80后 16.97% / 95后 14.97% · 城市等级 Tier3 25.05% / Tier2 20.02% / Tier1New 17.58% · 是否多人口 否 95.27% / 是 4.73%。R48:性别 男 69.18% / 女 30.82%(不存在"未知 / 空值"档位)。

分类合并规则(洞察 ↔ 火山)

| 源 | 分类列 | 取值 | |---|---|---| | 洞察 CSV | 标签大类 | 业态到访 / 人群标签 / 驾驶行为 | | 火山 | 标签分类 | 人群标签 / 内容偏好 / 汽车内容关注 / 消费行为 |

  • 按分类名合并:两源都有「人群标签」→ 合成一个 TAB(13+10=23);仅一方有的分类 → 各自新建 TAB。
  • ⚠️ 两源标签名无交集(实测交集为空),合并只是并列放同一 TAB 的二级 chip,不涉及同名去重。
  • ⚠️ 不做来源区分(用户 2026 裁定):不标"洞察/火山"徽章、不用蓝紫双色,统一蓝色条形并列。

车机流量使用(4 指标分块)

  • 二级 chip = 月活 / 活跃天数 / 用量 / 使用时长,每块内按该指标独立降序展示全部二级类型。
  • 条形 = 档内相对值,右侧 = 真实数值,底部标注"XX 最高:YY"。
  • (演进史:早期单块按使用时长排序 + 右侧附四列明细 → 用户要求拆成 4 块分别降序。)验证:月活 TOP1=行车辅助 0.78 / 活跃天数 TOP1=行车辅助 24.7 / 用量 TOP1=行车辅助 696.2 / 使用时长 TOP1=地图导航 4,567。

APP 使用排名

  • 二级 chip = 5 种排名方式(APP使用人数排名 / 人均使用天数 / 人均使用时长 / 人均使用次数 / 人均使用流量),每方式 TOP1~TOP15。
  • ⚠️「占比」列量纲随方式变化:使用人数排名 ≈ 0~1(比例),人均流量 / 时长是绝对值(如 1356.0)。原样展示,不换算、不解释、不归一。

商圈到访(单 TAB 三层平铺下钻)—— ⚠️ 省份不是 Tab,城市也不在 chip 里

  • 一级 TAB =「商圈到访」一个(角标 = 省份数),面板内先平铺展示全部省份方块(tile:省名 + 城数)。
  • 点省份 tile → 平铺该省城市清单(tile:城市名),顶部「← 返回省份」。
  • 点城市 tile → 展示该城市全部 Top1-10 商圈,顶部「← 返回城市」。⭐ 末层 = 明细表格 .mtable(排名 | 商圈名称),不画柱状图 / 色条(用户 2026-09-14 裁定;旧版是一条占满 100% 的假色条,已废弃)。
  • 实现:_mall_table(g) 生成明细表;_mall_tabs() 生成 .mall-layer[data-layer=root|p{i}|c{i}_{j}] 三层 DOM + mallProv/mallCity/mallBack/mallCityBack JS 切换 display;CSS .dgrid(auto-fill 网格)+ .dstile(方块)+ .backbtn + .mtable(明细表)。
  • 城市 → 省份映射 scripts/_city_province.py(city_to_province() + PROVINCE_ORDER),覆盖 339 城 0 未映射(含直辖市/自治州/盟/地区/新疆兵团县级市/海南省直辖县)。R48 = 30 省(广东 21 / 河南 18 / 四川 17 城最多)。
  • ⚠️ 排名等级 是 Top1~Top10 字符串,排序用 extract(r'(\d+)').astype(int),不能按字符串排(否则 Top10 排在 Top2 前)。
  • 演进史(别走回头路):①271 城全 chip(太多)→ ②省份 chip + 城市 chip → ③省份各占一 Tab(错)→ ④单 Tab 三层平铺下钻 → ⑤当前:末层由"占满 100% 的假色条"改为 .mtable 明细表(用户 2026-09-14 裁定,商圈无数值字段,禁止柱状图)。

渲染规范(用户 2026 裁定,通用)

  • 低占比必须用"档内相对条":问题 = 火山部分标签 TOP1 很低(手机品牌 ~24%、汽车用车场景 <1%),绝对宽度会塌成细线。修法 = 条宽 = 本档占比 ÷ 本标签最大占比 × 100%(最大档满条,其余按比例可见),右侧数值仍显示真实值(保真)。实现 _bars():mx=max(档位值); w=p/mx*100,CSS .bar{min-width:3px}。
  • 二级 chip 切换的 JS showTag(g, el) 必须基于 el.closest('.panel') 定位作用域(多 TAB 各有自己的 chip / tp,用全局 data-p 前缀匹配会串台)。
  • HTML 结构设计通则见 echarts-html-report 技能:references/tab-structure-and-drilldown.md(哪一层该做 TAB、哪一层在面板内下钻、chip 作用域 bug)。本技能的商圈三层下钻就是该通则的一个实例。

实现脚本与坑

  • python3 scripts/_profile_report_v2.py <pack> [--data-root <目录>] [--out <输出目录>] → <pack>_画像总览_全标签.html(默认落在当前工作目录)。
  • 核心函数:load_csv_pack() / load_volcano_pack() / load_demographics() / load_traffic() / load_app_rank() / load_mall_by_province() / _mall_table() / _mall_tabs() / _ranking_panels() / _bars()。
  • ⚠️ 命中人数_总和 是带千分位逗号的字符串(如 '1,026.48'),必须 str.replace(',','',regex=False) 再 pd.to_numeric,否则 ValueError。
  • ⚠️ 火山占比列名是 标签值占比(不是 标签占比)。

已验证实例

  • R48:48 个洞察标签 + 64 个火山标签,10 个一级 TAB;经济水平:中低 26.49% / 中 25.86% / 中高 22.44% / 低 18.74% / 高 6.46%。
  • R4 单包实测:车机流量 87 类(比 R48 的 65、R64 的 80 都多);人生阶段 未知 65.50% / 求职 14.17% / 婴儿家长 8.48% / 中小学生家长 6.64%。R64:车机流量 80 类,人生阶段 未知 56.88% / 求职 28.07%。
  • 各包车机流量"二级类型"条数因包而异(65/80/87…),不是全量 92 类,勿硬编码。

场景3:多人群包画像差异对比(「R64 和 R48 / 多个包的画像差异」)

# ⭐ 多包 = 合并进【同一份】HTML,绝不拆成多个文件
python scripts/_profile_compare_v2.py R64 R48 5          --data-root "<数据目录>" --out "<输出目录>"
python scripts/_profile_compare_v2.py R64 R48 R4 R40 5   --data-root "<数据目录>" --out "<输出目录>"
# 位置参数 = 人群包列表(≥2);若最后一个位置参数是数值,则作为显著性阈值(百分点),默认 5.0
# → <输出目录>/R64_vs_R48_画像差异_全标签.html
# → <输出目录>/R64_vs_R48_vs_R4_vs_R40_画像差异_全标签.html

口径(权威 v2 · 2026-09-14 升级为多包合并对比)

  1. 数据源 = 仅标签类两源(洞察 CSV + 火山)。⭐ 车机流量 / APP 排名 / 商圈到访不参与对比(用户 2026 裁定)。开关:INCLUDE_RANKING_IN_COMPARE = False(改 True 可恢复,cmp_traffic/cmp_app/cmp_mall 仍保留)。
  2. ⭐ 多包 = 合并进同一份报告(绝不输出多个 HTML):用户提到几个包(≥2),一律放进【同一份】HTML 里并排对比;函数 build(packs, thresh) 接收包列表。⚠️ 废弃旧版"多包 3+ 循环两两对比"做法——不再为每个两两组合单独出 HTML。
  3. ⭐「基础属性」Tab 置顶(用户 2026 补充):性别 / 年龄段 / 城市等级 / 是否多人口 4 维,用 compare_tags([load_demographics(p) for p in packs], packs, thresh) 对比(N 包通用),同样享受 TAB 侧高亮(维度有显著档位 → chip 橙框 + 角标)。
    • → 对比报告 = 7 个一级 TAB:基础属性 + 6 个标签大类。
  4. 标签对齐:所有有效包各取同名分类、同名标签(取交集);档位取并集(缺的一方记 0.00%)。
  5. 逐档「极差」 = 各包占比最大值 − 最小值(百分点);极差 ≥ 阈值 → 该行橙底高亮 + 左边框。⚠️ 2 包时 极差 = |A占比% − B占比%|(等价旧 |Δ|);N>2 时衡量整组离散度。差异一律在表格末列直接体现:2 包 → 「差值(A−B)」带正负号;3+ 包 → 「极差」即极值。
  6. 展示形式 = 纯表格,不用柱状图(早期双条图已废弃)。函数 _table_cmp(info, packs, thresh),CSS 类 .ctable:
    • 2 包:列 = 档位 | 包A% | 包B% | 差值(A−B),末列 = 包A − 包B【带正负号、带 %,正 = 包A 占比更高】。
    • 3 包及以上:列 = 档位 | 包1% | 包2% | … | 极差,末列极差 = 各包最大 − 最小(极值,百分点,带 %)。
  7. ⭐ TAB 侧高亮(用户核心诉求):二级 chip 有显著档位 → 橙框橙字 + 角标显示显著档位数;一级 TAB 下有显著标签 → 橙框 + 角标显示显著标签个数。无显著差异的 TAB 不加标记。
  8. ⭐ 结论文案(用户 2026 裁定,勿回退):由 _diff_note(info, thresh, unit, packs) 生成,罗列显著档位 + 各包取值 + 极差/差值(按极差降序):
    • 有显著(2 包)→ 该{标签|维度}有 N 个差异更为显著:<档位1>(包A x% / 包B y%,差 +z%)、…(z 带正负号 = 包A−包B;包名与百分比间留空格分隔)
    • 有显著(3+ 包)→ 该{标签|维度}有 N 个差异更为显著(极差≥5%):<档位1>(包1 x% / 包2 y% / …,极差 +z%)、…(包名与百分比间留空格分隔)
    • 无显著 → 此{标签|维度}差异值不显著(各档差异值均低于阈值;不再带未替换的变量占位符)
    • 标签类 unit='标签',基础属性 unit='维度'。
  9. 阈值默认 5.0 个百分点(擅自改成 3pp 会让显著行从 117 涨到 185,先问用户)。实测 TAB 显著标签数 5pp→3pp:人群标签 19→20、业态到访 14→15、驾驶行为 12→13、内容偏好 4→7、汽车内容关注 2→6、消费行为 7→9。
  10. 铁律同前:只给百分比与百分点差值,绝不出现命中人数绝对值。
  11. 查无包处理:脚本自动剔除查无数据的包,并在报告副标题注明"XX 查无数据,未参与对比";有效包不足 2 个则生成提示页(不报错退出)。这也满足铁律 / pitfall 11「多包必须回报查无包」。

各源对比口径差异(⚠ 排名类三源当前不参与;改 True 时适用)

  • 洞察 CSV / 火山标签:逐档「各包极差」(百分点),阈值 5pp 有意义。← 当前唯一启用的对比口径(N 包通用)
  • ⚠️ 排名类三源(车机流量 / APP 排名 / 商圈到访)若打开 INCLUDE_RANKING_IN_COMPARE,仍按「有效包中前两个」做两两对比(N>2 时不扩展),仅作补充,不影响标签主对比。
  • 车机流量:4 指标量纲是文件原值(非百分比),无统一阈值 → 判据 = |Δ| ≥ 该指标最大Δ的一半,展示差距 TOP30,单位随指标。
  • APP 排名:「APP使用人数排名」是比例(×100 → 百分点,可用阈值);其余方式(人均天数/时长/次数/流量)是绝对值,只排序不判定高亮。
  • 商圈到访:无百分比可比 → 判据 = 同城 Top3 商圈名单是否变化,变了即标"Δ";省份 tile 汇总该省变化城数。

已验证实例(可对答案)

  • R64 vs R48(5pp):7 个 TAB;TAB 显著数/总数 = 基础属性(2/4)、人群标签 19/23、业态到访 14/21、驾驶行为 12/14、内容偏好 4/22、汽车内容关注 2/17、消费行为 7/12;共 23 个共有标签 + 4 个基础属性维度;6 TAB 版本为 109 张对比表 / 244KB。(2 包时「极差」即旧称 |Δ|;3+ 包时为各包最大最小之差。)
  • R64 vs R48 基础属性:性别 男 R64 81.25% / R48 69.18%(Δ+12.07% 高亮)· 年龄段显著 7 档(05后 Δ+90.45%、85后 Δ-16.16%、95后 Δ-15.91%、90后 Δ-15.69%、80后 Δ-13.91%、75后 Δ-11.94%、70后 Δ-6.74%)· 城市等级 Tier5 12.79%/7.55%(Δ+5.24%)· 是否多人口无显著 → R64 显著年轻化(05后为主),R48 以 85-95 后为主力。
  • R4 vs R40(5pp):性别 男 R4 70.75%/R40 74.90%(Δ-4.15%)· 城市等级 Tier3 25.04%/17.75%(Δ+7.29%)、Tier1New 17.57%/25.65%(Δ-8.08%)、Tier5 12.32%/17.60%(Δ-5.28%)· 差异最大 = 偏好动力类型 增程式 Δ-92.22%、偏好价格区间 10~15w Δ+85.17% / 20~25w Δ-67.01% → R4 = 纯电动 + 10-15w,R40 = 增程式 + 15-25w,定位完全错开。
  • R64 vs R48 人生阶段(表格式示范):未知 56.88%/62.56%(Δ-5.68%)· 求职 28.07%/15.91%(Δ+12.16%)· 中小学生家长 11.36%/5.55%(Δ+5.81%)· 婴儿家长 2.40%/10.47%(Δ-8.07%)→ 4 个档位差异明显。

演进史(别走回头路)

  • 旧版 _profile_report.py compare 只覆盖洞察 CSV 标签、无火山、无 TAB 侧高亮 → 已被 v2 取代(旧脚本已从 skill 删除)。
  • 差异展示形式:①早期左右双条柱状图(_bars_cmp)→ ②当前:纯表格 档位|包1%|包2%|…|极差。用户裁定:对比不要柱子图,直接看百分比差异。
  • 对比范围:①早期含排名类三源(9 TAB)→ ②当前:基础属性 + 6 个标签大类(7 TAB),排名类三源不参与对比。
  • 多包输出形态(2026-09-14 升级):①旧版只支持 2 包单文件 + 3+ 循环两两出多个 HTML → ②当前:无论几包(≥2)一律合并为【一份】HTML(列 = 各包占比 + 极差),不再输出多个 HTML 文件。用户明确反对"多包拆多个 HTML"。
  • ⭐ 差异列口径(2026-09-14 用户裁定 → 本次修订):①早期对 2 包也展示「极差」列 → ②用户曾裁定 2 包不展示极差列(仅两包占比并排)→ ③当前:2 包展示带符号「差值(A−B)」列(正 = 包A 占比更高),3 包及以上展示「极差(极值)」列。差异一律在表格末列直接体现,用户要求能看到正负号。

数据定位(换机器 / 换使用方必读)

  • SKILL.md / references 里不写死绝对路径。数据统一由 scripts/resolve_data.py 定位,优先级: --data/--data-root > 环境变量 STW_DATA_ROOT > skill 根 config.json 的 data_root > 默认候选路径。
  • 所有脚本(含排名类三表 / 场景2 / 场景3)都走 resolve_data.resolve_file(文件名) 取数,不要求数据与脚本同目录,HTML 默认落在当前工作目录、可用 --out 指定。
  • 新环境上手:配指路 → 跑 python scripts/verify_data.py(体检 + canonical 锚点校准,约 1 分钟)→ 全绿才出数。
  • canonical 闸门:R8 × 经济水平 = 中 31.33 / 中低 27.71 / 中高 18.02 / 低 20.18 / 高 2.77(±0.05)。偏离 = 数据版本变了或口径被改,先排查再回答。
  • 完整移植说明、分发清单、常见坑见 references/porting-and-data-access.md。

数据接入与多文件

  • 6 张原始 xlsx 逻辑同构但画像字段集合不同;用户裁决:缺的列整列为空(NaN),不用占位符;判断"缺维"以"当前数据是否有该列 / 该列是否全空"为准。
  • 统一合成 洞察数据_full.csv(用户已按最大公共表头拼接):筛选 / 对比 / 模糊匹配统一在一个 DataFrame 上做。
  • 数据文件可能更新(如人群标签3 初期主表叫 Sheet3,后新增 Sheet1)。用 mtime / 行数校验缓存,不能沿用旧的 pickle。

洞察数据_full.csv 实测档案(2026-09 验证,文件改动后需重测)

  • 编码 = GBK(非 utf-8,用 encoding='utf-8' 会 UnicodeDecodeError)。
  • 真实列序:人群包编号, 性别, 年龄段, 动力类型, 标签大类, 标签名称, 标签值, 城市等级, 经济水平, 是否多人口家庭, 命中人数_总和。
  • 规模:约 435 万行、110 个人群包编号(60 个 R 车型 + 50 个细分市场编码)。单包单标签逐块读取约 6-7s 出结果;场景2 单包报告约 15-20s。
  • 详见 references/real-data-profile.md。

实现提示

pandas 最小骨架(口径 A)

sub = df[
    (df['人群包编号'].str.strip() == 'R5')          # 或多包 isin / 模糊 contains('A0-SUV')
    & (df['性别'] == '女')
    & (df['年龄段'] == '90后')
    & (df['城市等级'].astype(str).str.replace(' ', '', regex=False) == 'Tier1New')  # 城市实测带空格,先归一化
    & (df['标签名称'] == '经济水平')
]
pv = sub.pivot_table(
    index=None,                 # 多包分组时改为 '人群包编号'
    columns='标签值',
    values='命中人数_总和',
    aggfunc='sum', fill_value=0,
)
pct = (pv.div(pv.sum(axis=1), axis=0) * 100).round(2)

口径 B 最小骨架(画像构成)

snap = df[(df['人群包编号'].str.strip() == 'R5') & (df['标签名称'] == '经济水平')]
# 铁律 2:性别 = 空值 / NaN / null / 未知 的行整行剔除(4 个维度一视同仁)
g_txt = snap['性别'].astype(str).str.strip()
snap = snap[snap['性别'].notna() & ~g_txt.str.lower().isin(
    {'', 'nan', 'none', 'nat', 'null', '未知', 'unknown'})]
# 目标画像列自身的空值 / NaN 档位同样不展示
v_txt = snap['年龄段'].astype(str).str.strip()
v_keep = snap['年龄段'].notna() & ~v_txt.str.lower().isin({'', 'nan', 'none', 'nat', 'null'})
snap, v_txt = snap[v_keep], v_txt[v_keep]
g = snap.groupby(v_txt)['命中人数_总和'].sum()
pct = (g / g.sum() * 100).round(2).sort_values(ascending=False)

大文件处理

for chunk in pd.read_csv(path, encoding='gbk', chunksize=500_000, dtype=str,
                         usecols=lambda c: c in NEEDED_COLS):
    process(chunk)
  • 单 xlsx ~40-60s,可缓存但必须校验更新时间。读全量记得 pd.to_numeric(..., errors='coerce')。

输出模板(严格遵守铁律 0c)

  1. 百分比表格(核心交付):各档 %,各档和 ≈ 100%。
  2. 一句结论:只写数据本身直接显示的结构性事实(如"中 + 中低 + 低 为主力,R8 偏中低端")。不写量化推断、不提绝对量、不补口径说明。
  3. 柱状图:横轴 = 档位、纵轴 = 百分比、每柱带 % label。⛔ 商圈到访不做图,改为明细表格(见铁律 3 例外)。
  4. 必要警示(如有):空态 / 缺维 / 样本过少,按铁律 4 措辞,且不含任何数量词。

交付物与目录规整(用户偏好)

  • 用户在数据目录里只想看到报告和数据,不喜欢一堆 .py 脚本散落 → 脚本统一收在 skill 的 scripts/(本技能)或数据目录的 _问数脚本/ 子目录。
  • 报告留在数据主目录(用户直接双击看的地方):<pack>_画像总览_全标签.html、<A>_vs_<B>_画像差异_全标签.html。用 --out 直接指定输出目录即可,不必再 mv。
  • 删掉中间产物:调试脚本(_diag*)、旧版报告、预览截图 PNG 等,交付后清理。用户说"这个路径下生成了很多 py 文件,这个必须要展示吗" = 要你主动清理,不是问技术问题。
  • ⚠️ ~$xxx.xlsx(约 165 字节)是 Excel 打开时的锁文件,用户本机还开着表,不要动,关掉 Excel 会自动消失。

Pitfalls(实测踩坑)

  1. 同名双字段陷阱:表里既有画像列 经济水平,又有 标签名称 == 经济水平 的行。务必先判定此刻它是标签 / 画像筛选 / 快照锚点。
  2. 缺档 ≠ 错了:很多画像子组合在标签下并非全档齐(尤缺"高",城市越下沉越缺)。分母只计实际出现档。
  3. 分母不固定档数:分母 = 该标签实际出现档位的命中之和,不是固定 5 档 / N 档,否则会算成 <100%。
  4. 多包合并 vs 分组:合并整体(形态 3)与分组各自归一(形态 4)两种口径别混塞。
  5. 命中人数是加权数:小数正常,别取整造成失真;勿拿它与别文件总数 cross-check 人头。
  6. 脏值 / 数据更新:画像列出现 '0' / '1' 导出占位脏值、跨文件 dtype 摇摆。判断缺失用列存在性 + isna,勿依赖 dtype。动力类型 列混入整段人群包编号词,勿做精确匹配。
  7. sheet 名别写死:不同文件第一个表叫 Sheet1 或 Sheet3。火山固定 Sheet2。
  8. 多维切片重复展开 ≠ 要你去重(勿过度修正):整群原子会按 城市 × 年龄段 × 性别 × 是否多人口家庭 展开成多行,同值(如 1.369)在 Tier 1~Tier 5 各出现一次,直觉像重复计数。但 Q9 已裁定:直接对标签筛选后所有行按标签值求和占比,不必关心总人数、不要额外去重(去重反而算错、且与用户 Excel 透视对不齐)。只有提问带明确画像筛选时,才按筛后子集算。
  9. 用户问的标签名 ≠ 数据里的标签名(先探后算):用户口语 / 行业叫法与 标签名称 枚举常有出入(实例:问"出行时间段",数据里叫 驾驶时间段)。算之前先 dump 一次该人群包(±性别)下所有 标签名称 清单,确认有对应项再下手;找不到完全一致的 → 取最贴近的并在交付里标注"按 XX 标签计算的",或返回缺维提示让用户选。不要凭问句臆测一个不存在的标签名去筛(会静默出 0 行,极易误判为"该人群无此分布")。
  10. 数值列名是 命中人数_总和(不是 命中人数),且是带千分位逗号的字符串('1,026.48'),必须 str.replace(',', '', regex=False) 后再 pd.to_numeric,否则 ValueError。
  11. 多包查询必须回报"查无的人群包":多包模式下静默丢弃不存在的编号,问"R5 和 R6 分别怎样"时只出 R5 一行,会被误读成"另一个包没数据"。实例:本数据没有 R6(只有 R60–R69),问多包前先枚举 人群包编号 确认。
  12. canonical 问题 2(四维深筛)在本数据是真空态,不是 bug:R5 + Tier1New + 女 + 90后 + 经济水平=高 → 话费区间 逐级叠加后为 0 行(经济水平=高 在话费区间子集上本就极稀疏)。遇到空态先按 pitfall 9 排除标签名写错,再确认是真实稀疏,然后按铁律 4 措辞回复。
  13. 场景2/3 渲染:showTag(g, el) 必须基于 el.closest('.panel') 定位作用域(多 TAB 各有自己的 chip,用全局 data-p 前缀匹配会串台);f-string 里不要嵌带反斜杠的表达式(会 SyntaxError),先把 HTML 片段赋值给变量再插值。
  14. 火山占比和 ≠ 1 不要误修:多选型 >1、单选型 ≈0.93、条件型 ≪1 都是正常语义,全局归一会把 0.3% 放大成 100%。详见《第二数据源》节与 references/volcano-data-source.md。
  15. 城市名精确匹配:口语"北京"→ 数据是"北京市";先 mall_cities(pack) 查候选再筛,别直接拿口语串去做 eq。
  16. 同一文件的多处 Edit 必须串行下发:并行编辑同一脚本会互相覆盖(曾导致 F_APP → _p(F_APP) 只替换了一半,报 FileNotFoundError)。改完务必 grep 复查所有路径引用都已落盘。
  17. NaN 别靠 astype(str) != 'nan' 判断:astype(str) 会把 NaN 变成字符串 'nan',但空串 / None / NaT 各有各的写法,且真实业务里也可能有 'null'。判空统一用 s.notna() & ~s.astype(str).str.strip().str.lower().isin({'', 'nan', 'none', 'nat', 'null'})(性别额外加 '未知')。

references

  • references/schema-and-canonical-queries.md — schema / 值域、4 个 canonical 问题精确步骤 + 口径 B 题、路径汇总。
  • references/verified-examples-and-user-rulings.md — 已验证对齐的数值(R8 经济五档 % 等)、Q1–Q14 逐条裁定、脏值 / 缺失列 / CSV 合并说明。
  • references/real-data-profile.md — ✅ 洞察数据_full.csv 真实列序 / 编码 / 规模、R8 与 R5 回归锚点、城市等级带空格写法、多维切片重复值陷阱、小样本案例。
  • references/volcano-data-source.md — ✅ 火山完整数据_202608 档案:与洞察 CSV 是两种形态(结果表 vs 需计算)、Sheet2 陷阱、占比和≠1 的三类语义、模糊匹配标签名、"直接展示不计算"口径。
  • references/ranking-data-sources.md — ✅ 排名类三表(车机流量_8 / APP使用排名_9 / 商圈排名_10)档案:列结构、三种问法口径、_ranking_query.py 用法、排名排序与占比量纲陷阱、R48 已验证实例。
  • references/profile-overview-and-compare.md — ✅ 场景2 / 场景3 口径、脚本函数清单(含 load_demographics / _diff_note)、基础属性 TAB 规则、TAB 侧高亮、阈值实测对照、千分位逗号与 f-string 反斜杠陷阱、运行方式。
  • references/porting-and-data-access.md — ✅ 换机器 / 换使用方:数据定位优先级、verify_data 校准闸门、一致性保证做法、分发清单、常见坑。

scripts

  • scripts/resolve_data.py — 数据定位器(环境变量 / config.json / 默认候选路径 / --data-root),换机器不靠硬编码路径。新增 resolve_file(文件名) 供排名类三表 / 场景2 / 场景3 脚本取数。
  • scripts/verify_data.py — 数据体检 + canonical 锚点校准闸门,换环境 / 数据更新后必跑。
  • scripts/run_query.py — 口径 A(标签档位分布)端到端参考脚本:--pkg/--tag/--filters/--mode、GBK 读取、模糊 / 精确人群包匹配、merge / group 两种形态、小样本判定;--profile-col 走口径 B、--list-tags 先探后算。
  • scripts/run_volcano_query.py — 火山数据源:人群包 × 标签模糊匹配后直接列出 取值 + 占比,不做归一、不补档、不解释。
  • scripts/_ranking_query.py — ✅ 排名类三表查询(traffic / app_rank / app_methods / mall_rank / mall_cities),支持 --data-root。
  • scripts/_profile_report_v2.py — ✅ 场景2 权威实现:单车型完整洞察报告(10 TAB = 基础属性 + 6 标签大类 + 车机流量 + APP 排名 + 商圈)→ <pack>_画像总览_全标签.html;含 load_demographics()。
  • scripts/_profile_compare_v2.py — ✅ 场景3 权威实现 (v2):多包差异对比(7 TAB = 基础属性 + 6 标签大类,TAB 侧高亮,纯表格,含 _diff_note)。⭐ N 包(≥2)合并为一份 HTML(build(packs, thresh) 接收包列表,compare_tags 算各包占比 + 极差),→ <A>_vs_<B>[_vs_...]_画像差异_全标签.html,绝不输出多个 HTML。
  • scripts/_city_province.py — ✅ 城市 → 省份映射(覆盖商圈数据全部 339 城,0 未映射)+ PROVINCE_ORDER,被场景2 引用,必备。
  • scripts/_list_label_catalog.py — ✅ 场景零(标签目录速查):去重 + 分类输出库内全部标签(洞察按 标签大类、火山按 标签分类)与三张排行榜 + 人群包总数,默认 Markdown 直接贴对话、--json 出结构化;全程不读命中人数、不算占比(铁律 0 天然满足)。