上通五人群标签智能问数 (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 个百分点""样本量较少注意"这类拿命中数去比或用暗示量级的措辞,全部禁止。
- ❌ 不在表后"补充口径解释""我如何筛选"等多余说明,除非用户主动问口径。
- ✅ 若要提示小样本 / 仅供参考,只能用不含任何数量的话("样本过少,结果仅供参考");其余画面留给百分比表。
其余铁律
- 权威口径先于猜测:先读
说明.txt/说明.docx,其"常见提问"段是权威透视口径,不要凭直觉猜透视逻辑。 - 画像构成分布 → 先剔除「性别无效行」再分组:问某人群包"性别 / 年龄 / 城市 / 是否多人口 分布"时,性别为 空值 / NaN /
null/未知的行整行剔除(不参与计算、不展示,分母随之减小),其余按目标画像列分组归一。- 4 个维度一视同仁:性别、年龄段、城市等级、是否多人口 全部先做这一步("未知"与空值是性别列的孤儿值,会污染按其他维度的构成)。
- 无例外:即便问的就是"性别分布",未知 / 空值档位也不展示,直接忽略(用户 2026-09-10 裁定)。
- 各维度自身的空值 / NaN 档位同样不展示(不允许出现空白标签的柱子)。
- 实现:
_profile_report_v2.load_demographics()与run_query.compute_profile()已内建;无效值集合{'', 'nan', 'none', 'nat', 'null'},性别额外加{'未知','unknown'}。
- 可视化默认柱状图:横轴 = 档位、纵轴 = 百分比(带 % label)。可再给饼图 / 折线图。HTML 报告里低占比档位必须用"档内相对条"(条宽 = 本档 ÷ 本标签最大档),数值仍显示真实值。
- ⛔ 唯一例外 = 商圈到访(用户 2026-09-14 裁定):商圈数据只有「排名等级 + 商圈名称」两个文本字段、没有任何数值,画条只能是假的 100%,毫无信息量。一律用明细表格展示,不画柱状图 / 色条——报告里末层用
.mtable表格,对话里直接给 Markdown 明细表。此例外覆盖本条默认规则。
- ⛔ 唯一例外 = 商圈到访(用户 2026-09-14 裁定):商圈数据只有「排名等级 + 商圈名称」两个文本字段、没有任何数值,画条只能是假的 100%,毫无信息量。一律用明细表格展示,不画柱状图 / 色条——报告里末层用
- 三类友好提示,措辞区分:
- 空态(所选画像条件组合查无切片 / 0 行):提示"该筛选条件下没有满足的数据(请放宽或减少筛选维度)",输出空。
- 缺维(数据文件 / 整列压根没有某画像字段):先提示"该数据缺少 X 维度",仍展示"忽略该维度"后的分布。
- 样本过少(非 0 行但命中合计极小、档位塌缩,如 R9 / R9+ 一线):给 % 并附"样本过少,结果仅供参考"一句即止,绝不解释绝对量。
- 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):- 单包单标签(R8 经济水平)
- 多画像条件筛单标签(R5 + 新一线 + 90后 + 女 + 经济高 → 话费区间)
- 多包整体合并(R5 + R6 合并为一个整体)
- 多包分组各自归一(R5 / R6 各出一份分布)
透视口径 B — 画像构成分布(经济水平做锚点)★ 用户场景更新后敲定
当问句宾语是画像列本身("R5 的性别 / 年龄段 / 城市等级 / 是否多人口 分布""R5 的人群画像构成")时,走本口径:输出的不是经济水平 5 档表,而是目标画像列各值的构成占比。
- 锚点:把
经济水平标签行当作该人群包的完整人群覆盖快照(经济水平全 5 档齐、覆盖所有画像切片、数据正交)。 - 取数:
sub = df[(df['人群包编号'].str.strip() == R#) & (df['标签名称'] == '经济水平')]。 - 剔除性别无效行:
空值 / NaN / null / 未知的性别行整行剔除,不参与计算也不展示(分母随之减小)。4 个维度都要先做这一步(场景2 基础属性 TAB 的load_demographics()已内建)。 - 分组:按目标画像列(性别 / 年龄段 / 城市等级 / 是否多人口家庭)分组,
命中人数_总和求和 = 分子;分母 = 该列所有值的命中和(= 快照人群总量)。 - 占比 % = 分子 / 分母 × 100。经验证:各画像列独立求和互相相等、且等于经济水平全档和 → 任意单列分组占比天然归一到 100%,可直接交付。
- 交付:每维一张百分比表(先 % 表 → 再一句数据驱动的结论),可配柱状图。绝不吐露命中绝对值。
口径 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 |
三种问法口径
- 车机流量使用 → 直接取该包
二级类型 × 月活/活跃天数/用量/使用时长,原样展示(不归一、不合列)。 - APP 使用排名 → 未指定方式则按 5 种方式分开展示(APP使用人数排名 / 人均使用天数 / 人均使用时长 / 人均使用次数 / 人均使用流量);指定方式只出该张。排序用
排名序号里的数字(str.extract(r'(\d+)')转 int),不能按字符串排(否则 TOP10 排到 TOP2 前)。⚠️「占比」列量纲随方式变化:人数排名 ≈ 0~1 比例,人均流量 / 时长是绝对值(如 1356.0),原样展示不换算。 - 商圈到访 → 指定城市展示该城 Top1-10;未指定则展示该包全部城市。城市名须精确匹配数据写法(口语"北京" → 数据里是"北京市"),先用
mall_cities(pack)查候选再筛(R48 有 271 城)。- ⛔ 展示形式 = 明细表格,不画柱状图 / 不画色条(用户 2026-09-14 裁定)。该源只有
城市 / 商圈名称 / 排名等级三个文本列,没有数值,任何条形图都是假的 100%。 - 对话输出 = Markdown 明细表:指定城市 →
| 排名 | 商圈名称 |;未指定城市 →| 城市 | 排名 | 商圈名称 |(全部城市,行数很大时按 Top10 原样列出,不做裁剪 / 不解释)。
- ⛔ 展示形式 = 明细表格,不画柱状图 / 不画色条(用户 2026-09-14 裁定)。该源只有
铁律仍适用:三表本身是结果表,里外都不得出现任何命中人数绝对值;"占比"列是文件自带字段可原样展示,但绝不可附加任何自算的绝对量。
已验证实例(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/mallCityBackJS 切换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 升级为多包合并对比)
- 数据源 = 仅标签类两源(洞察 CSV + 火山)。⭐ 车机流量 / APP 排名 / 商圈到访不参与对比(用户 2026 裁定)。开关:
INCLUDE_RANKING_IN_COMPARE = False(改 True 可恢复,cmp_traffic/cmp_app/cmp_mall仍保留)。 - ⭐ 多包 = 合并进同一份报告(绝不输出多个 HTML):用户提到几个包(≥2),一律放进【同一份】HTML 里并排对比;函数
build(packs, thresh)接收包列表。⚠️ 废弃旧版"多包 3+ 循环两两对比"做法——不再为每个两两组合单独出 HTML。 - ⭐「基础属性」Tab 置顶(用户 2026 补充):性别 / 年龄段 / 城市等级 / 是否多人口 4 维,用
compare_tags([load_demographics(p) for p in packs], packs, thresh)对比(N 包通用),同样享受 TAB 侧高亮(维度有显著档位 → chip 橙框 + 角标)。- → 对比报告 = 7 个一级 TAB:基础属性 + 6 个标签大类。
- 标签对齐:所有有效包各取同名分类、同名标签(取交集);档位取并集(缺的一方记
0.00%)。 - 逐档「极差」 = 各包占比最大值 − 最小值(百分点);
极差 ≥ 阈值→ 该行橙底高亮 + 左边框。⚠️ 2 包时 极差 =|A占比% − B占比%|(等价旧 |Δ|);N>2 时衡量整组离散度。差异一律在表格末列直接体现:2 包 → 「差值(A−B)」带正负号;3+ 包 → 「极差」即极值。 - 展示形式 = 纯表格,不用柱状图(早期双条图已废弃)。函数
_table_cmp(info, packs, thresh),CSS 类.ctable:- 2 包:列 =
档位 | 包A% | 包B% | 差值(A−B),末列 = 包A − 包B【带正负号、带 %,正 = 包A 占比更高】。 - 3 包及以上:列 =
档位 | 包1% | 包2% | … | 极差,末列极差 = 各包最大 − 最小(极值,百分点,带 %)。
- 2 包:列 =
- ⭐ TAB 侧高亮(用户核心诉求):二级 chip 有显著档位 → 橙框橙字 + 角标显示显著档位数;一级 TAB 下有显著标签 → 橙框 + 角标显示显著标签个数。无显著差异的 TAB 不加标记。
- ⭐ 结论文案(用户 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='维度'。
- 有显著(2 包)→
- 阈值默认 5.0 个百分点(擅自改成 3pp 会让显著行从 117 涨到 185,先问用户)。实测 TAB 显著标签数 5pp→3pp:人群标签 19→20、业态到访 14→15、驾驶行为 12→13、内容偏好 4→7、汽车内容关注 2→6、消费行为 7→9。
- 铁律同前:只给百分比与百分点差值,绝不出现命中人数绝对值。
- 查无包处理:脚本自动剔除查无数据的包,并在报告副标题注明"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)
- 百分比表格(核心交付):各档 %,各档和 ≈ 100%。
- 一句结论:只写数据本身直接显示的结构性事实(如"中 + 中低 + 低 为主力,R8 偏中低端")。不写量化推断、不提绝对量、不补口径说明。
- 柱状图:横轴 = 档位、纵轴 = 百分比、每柱带 % label。⛔ 商圈到访不做图,改为明细表格(见铁律 3 例外)。
- 必要警示(如有):空态 / 缺维 / 样本过少,按铁律 4 措辞,且不含任何数量词。
交付物与目录规整(用户偏好)
- 用户在数据目录里只想看到报告和数据,不喜欢一堆
.py脚本散落 → 脚本统一收在 skill 的scripts/(本技能)或数据目录的_问数脚本/子目录。 - 报告留在数据主目录(用户直接双击看的地方):
<pack>_画像总览_全标签.html、<A>_vs_<B>_画像差异_全标签.html。用--out直接指定输出目录即可,不必再 mv。 - 删掉中间产物:调试脚本(
_diag*)、旧版报告、预览截图 PNG 等,交付后清理。用户说"这个路径下生成了很多 py 文件,这个必须要展示吗" = 要你主动清理,不是问技术问题。 - ⚠️
~$xxx.xlsx(约 165 字节)是 Excel 打开时的锁文件,用户本机还开着表,不要动,关掉 Excel 会自动消失。
Pitfalls(实测踩坑)
- 同名双字段陷阱:表里既有画像列
经济水平,又有标签名称 == 经济水平的行。务必先判定此刻它是标签 / 画像筛选 / 快照锚点。 - 缺档 ≠ 错了:很多画像子组合在标签下并非全档齐(尤缺"高",城市越下沉越缺)。分母只计实际出现档。
- 分母不固定档数:分母 = 该标签实际出现档位的命中之和,不是固定 5 档 / N 档,否则会算成 <100%。
- 多包合并 vs 分组:合并整体(形态 3)与分组各自归一(形态 4)两种口径别混塞。
- 命中人数是加权数:小数正常,别取整造成失真;勿拿它与别文件总数 cross-check 人头。
- 脏值 / 数据更新:画像列出现 '0' / '1' 导出占位脏值、跨文件 dtype 摇摆。判断缺失用列存在性 + isna,勿依赖 dtype。
动力类型列混入整段人群包编号词,勿做精确匹配。 - sheet 名别写死:不同文件第一个表叫 Sheet1 或 Sheet3。火山固定
Sheet2。 - 多维切片重复展开 ≠ 要你去重(勿过度修正):整群原子会按 城市 × 年龄段 × 性别 × 是否多人口家庭 展开成多行,同值(如 1.369)在 Tier 1~Tier 5 各出现一次,直觉像重复计数。但 Q9 已裁定:直接对标签筛选后所有行按标签值求和占比,不必关心总人数、不要额外去重(去重反而算错、且与用户 Excel 透视对不齐)。只有提问带明确画像筛选时,才按筛后子集算。
- 用户问的标签名 ≠ 数据里的标签名(先探后算):用户口语 / 行业叫法与
标签名称枚举常有出入(实例:问"出行时间段",数据里叫驾驶时间段)。算之前先 dump 一次该人群包(±性别)下所有标签名称清单,确认有对应项再下手;找不到完全一致的 → 取最贴近的并在交付里标注"按 XX 标签计算的",或返回缺维提示让用户选。不要凭问句臆测一个不存在的标签名去筛(会静默出 0 行,极易误判为"该人群无此分布")。 - 数值列名是
命中人数_总和(不是命中人数),且是带千分位逗号的字符串('1,026.48'),必须str.replace(',', '', regex=False)后再pd.to_numeric,否则 ValueError。 - 多包查询必须回报"查无的人群包":多包模式下静默丢弃不存在的编号,问"R5 和 R6 分别怎样"时只出 R5 一行,会被误读成"另一个包没数据"。实例:本数据没有 R6(只有 R60–R69),问多包前先枚举
人群包编号确认。 - canonical 问题 2(四维深筛)在本数据是真空态,不是 bug:
R5 + Tier1New + 女 + 90后 + 经济水平=高 → 话费区间逐级叠加后为 0 行(经济水平=高 在话费区间子集上本就极稀疏)。遇到空态先按 pitfall 9 排除标签名写错,再确认是真实稀疏,然后按铁律 4 措辞回复。 - 场景2/3 渲染:
showTag(g, el)必须基于el.closest('.panel')定位作用域(多 TAB 各有自己的 chip,用全局 data-p 前缀匹配会串台);f-string 里不要嵌带反斜杠的表达式(会 SyntaxError),先把 HTML 片段赋值给变量再插值。 - 火山占比和 ≠ 1 不要误修:多选型 >1、单选型 ≈0.93、条件型 ≪1 都是正常语义,全局归一会把 0.3% 放大成 100%。详见《第二数据源》节与
references/volcano-data-source.md。 - 城市名精确匹配:口语"北京"→ 数据是"北京市";先
mall_cities(pack)查候选再筛,别直接拿口语串去做 eq。 - 同一文件的多处 Edit 必须串行下发:并行编辑同一脚本会互相覆盖(曾导致
F_APP→_p(F_APP)只替换了一半,报 FileNotFoundError)。改完务必grep复查所有路径引用都已落盘。 - 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 天然满足)。
Scan to join WeChat group