Skip to content

漫跑数据 · 可视化操作手册 ​

本手册只记录可视化层:从数据库长表 → Tableau Prep 流程 → .hyper 提取 → .twb 工作簿。 数据入库、建表、每周同步数据库的部分见 ../漫跑数据入库/数据库操作手册.md,本手册不重复。

所有描述都来自对现存文件的静态解析与独立复算。凡写"实测"的, 都是把源数据重新算过一遍、结果与文件内数值对齐的。

本手册写作期间(2026-10-17),漫跑数据分析.twb 被另存过一次 (450919 → 308235 字节)。第四章以后的内容以改动后的文件为准; 早期版本的痕迹只在问题清单里作为历史说明保留。

一条贯穿全文的原则:筛选器决定"看哪一段",不决定"系统是什么"。 换周期只改筛选器的区间,图表结构不用动。所以本手册讲筛选器时, 只讲它作用在什么字段、以什么顺序生效、影响哪些表,不评价具体区间选得对不对。


〇、先看这张图:可视化链路 ​

                       ┌──────────────────────────────────────────┐
                       │  MySQL  漫跑库.漫跑记录                  │
                       │  宽表:689 行 × 35 列                    │
                       │  学号 / 姓名 / 班级 + 16 天 × (开始,结束)│
                       └───────────────────┬──────────────────────┘
                                           │ Prep「漫跑记录」LoadSql
                                           ▼
   ┌──────────────────────────────────────────────────────────────────────┐
   │  Tableau Prep 流程:漫跑数据分析-v2.tfl                              │
   │                                                                      │
   │  主链:读库 → 清理1 → 转置-开始 ┐                                    │
   │                    └ 转置-结束 ┴→ 清理2/3 → 联接1 → 清理终 → 并集1 ─┐│
   │  旁链:旧格式-高一.csv → 转置5 → 清理旧 ────────────────────────────┴┤│
   │                                                                      ││
   └──────────────────────────────────────────────────────────────────────┘│
                                                                            ▼
                       ┌──────────────────────────────────────────┐
                       │  漫跑数据分析-2026R3.hyper                │
                       │  长表:13091 行 × 10 列                   │
                       │  日期 / 打卡状态 / 开始 / 结束 / 持续时间  │
                       └───────────────────┬──────────────────────┘
                                           │ .twb 数据源连接(hyper)
                                           ▼
                       ┌──────────────────────────────────────────┐
                       │  漫跑数据分析.twb                        │
                       │  6 张工作表 + 1 个空仪表板                │
                       └──────────────────────────────────────────┘

四段产物,职责分明:

环节文件内容谁改
源漫跑数据分析.xlsx689 人 × 16 天宽表,人工维护每周覆盖
库MySQL 漫跑库.漫跑记录同上,35 列04_导入更新.py
流程漫跑数据分析-v2.tfl13 个节点,转置 + 连接 + 并集结构变更时才动
提取漫跑数据分析-2026R3.hyper长表 13091 行,Tableau 直接读每次跑流程重生成
分析漫跑数据分析.twb字段、计算、图表改图时动

长表的意义:宽表一人一行,日期藏在列名里;长表一天一条,日期 变成真正的字段, 才能拖时间轴、算连续天数、做趋势。转置那一步就是把"列名里的日期"变成"行里的值"。


一、数据源现状(先对齐口径,否则后面所有图都会误读) ​

1.1 规模 ​

项值来源
班级 sheet16(另有 1 个「示例」空表头)漫跑数据分析.xlsx
学生689 人去重后的学号数
日期列32 列 = 16 天 × (开始,结束)表头
日期16 天:9 月 10 天 + 10 月 6 天表头
库内列数35 = 学号/姓名/班级 + 32SHOW COLUMNS
长表行数13091Hyper nullCounts 反推
长表列数10Hyper 元数据

1.2 16 个跑步日与当天实际记录数 ​

下表的"完整打卡"是 Excel 口径(开始、结束都非空):

#日期星期完整打卡库内备注
12026/09/10四180180全期最高,开学首日
22026/09/14一143143
32026/09/15二131131
42026/09/16三129129
52026/09/17四137137
62026/09/20日148148周日,疑为调休上课
72026/09/21一131131
82026/09/22二122122
92026/09/23三127127
102026/09/28一1081089 月末
112026/10/08四00两边都空
122026/10/09五00两边都空
132026/10/12一00两边都空
142026/10/13二570Excel 有、库里没有
152026/10/14三8585
162026/10/15四7575
合计15731516差 57,全部来自 10/13

两条关键结论:

  1. 真正全空的是 10/08、10/09、10/12(3 天),不是 4 天。 10/13 在 Excel 里有 57 条完整记录。
  2. 数据库落后于 Excel:10/13 那 57 条全部没有进库。 而 Prep 是从库里读的(LoadSql),所以 Hyper 只有 1516 条 —— 可视化链条看不到这 57 条。

任何"按天平均""出勤率"的计算,分母都不能直接用 16。

1.3 打卡记录数(分三个口径) ​

口径Excel数据库Hyper
开始 + 结束都齐全157315161516
只有开始565
只有结束333
负时长000
有任意值的记录157815221521

两处不一致:

  • 完整记录差 57:全部来自 10/13(Excel 有、库没有)。
  • 只有开始差 1:库里 260001 在 10/15 有一条只有开始的记录,Excel 该格为空。 这条在 Hyper 里没有出现(Hyper 开始时间 非空 1521 = 1516 + 5), 说明它是在 Hyper 生成之后才进库的。

Hyper 的 持续时间 非空数 = 1516,与数据库一致、比 Excel 少 57。 手册后续凡涉及"长表/工作簿"的数字,一律以 1516 为准;涉及"源数据全貌"的,用 1573。

1.4 覆盖情况 ​

  • 689 人中 256 人有过任意一条记录(37.2%),其中 254 人至少有一次完整打卡 (另 2 人只有半截记录:学生A、学生B)。其余 433 人全期空白。
  • 各班"有任意记录"人数(分母为班级人数):
班级有记录班级有记录
高一 1 班25 / 37高一 9 班19 / 43
高一 2 班13 / 44高一 10 班17 / 42
高一 3 班22 / 46高一 11 班19 / 44
高一 4 班24 / 46高一 12 班8 / 43
高一 5 班15 / 40高一 13 班16 / 44
高一 6 班15 / 44高一 14 班19 / 44
高一 7 班6 / 41高一 15 班12 / 44
高一 8 班20 / 43高一 16 班6 / 44

(班级列合计 256,与总人数一致。)

1.5 时间字段的口径 ​

开始时间 取值 13 ~ 55,结束时间 取值 19 ~ 60。

这不是"几点几分"的绝对时刻,而是某个整点小时内的分钟偏移:

证据数值
开始时间 最小值13(无 0–12)
开始时间 最大值55(无 60)
结束时间 最大值60(恰好 3 条)
结束时间 > 600 条
开始时间 分布峰值21(163 条)、25(144 条)、23(127 条)
结束时间 分布峰值30(112 条)、28(98 条)、31(97 条)
开始时间 不同取值42 个
结束时间 不同取值41 个

若是绝对时刻(0–23 时),不可能:整期没有 0–12 点、没有 13 时以前的记录,也不会出现 60。 若是"从某起点计的分钟数",则不应有 60 这个硬上界。 60 恰好是分钟的周期上界,且只有结束侧触及——这几乎只能是"某小时内第几分钟"。

因此:持续时间 = 结束时间 - 开始时间 只在同一小时内成立。 跨小时的记录(例如开始 55、结束 5)会被算成 5 - 55 = -50。 本数据集实测无负值,说明源数据在录入时已按同小时处理, 但这个前提没有写进任何计算字段,也没有写进流程注释。

仍有未知:不知道是哪一小时(早读前?课间?放学前?)。若需还原真实时刻, 必须知道该小时的起点,而数据里没有这个字段。

1.6 用时分布(实测) ​

以 Excel 全量(1573 条完整记录)计算:

指标值
最小1
最大35
均值8.55
中位数7
不同取值33
众数(前五)5(323 条)、6(287 条)、7(268 条)、8(165 条)、9(91 条)

1573 条里,用时 5–9 的占 1134 条(72.1%)。

若只统计库内/Hyper 的 1516 条(少了 10/13 的 57 条), 均值与中位数差异可忽略,但口径要对齐。

1.7 "全勤"与"打卡天数"(最容易出错的地方) ​

"全勤"在这个数据集里有 四种互不相等的算法,混用会出错:

口径判定实测人数
A. 9 月 10 天全勤10 个 9 月日期都有完整记录57
B. 13 天全勤(旧 3 + 9 月 10)旧 3 天有值 且 9 月 10 天完整46
C. 13 个有记录日全勤(Excel)除 3 个全空日外,13 天全部完整0
D. 19 天全勤(旧 3 + 主链 16)含 3 个全空日0

工作簿里 周期内是否全勤? 用的是 {FIXED [班级],[姓名] : MIN(...)} = 1, 它会随全局日期筛选器的区间变化:区间设成哪一段,"全勤"就是"哪一段里零缺勤"。 若区间覆盖旧 3 天 + 9 月 10 天,即上表的 B(46 人)。

"打卡天数"则有两个维度交叉,必须同时说清:

维度取值
时间范围13 天(旧 3 + 9 月 10) / 19 天(旧 3 + 主链 16)
打卡判定开始且结束(当前流程与工作簿统一口径) / 开始或结束(早期口径)

四种组合的人数分布(Excel 全量):

范围判定0 天10–13 天≥14 天≥10 天最大值
13 天且4009609613
13 天或3989609613
19 天且386594810716
19 天或384594810716

若按库内(缺 10/13 的 57 条)计算 19 天口径,人数会再少一些。

上表的用法:先确定"看哪段时间"(13 天还是 19 天),再确定"怎样算一次打卡"(且还是或)。 两个维度选错,人数就会差几十人。

例如同样是"19 天、且":

  • ≥10 天 有 107 人
  • 其中只有 59 人落在 10–13 天,另 48 人 ≥14 天

所以"排行里显示多少人"完全取决于筛选器上界设在哪,不能跨口径比较。


二、Tableau Prep 流程详解(漫跑数据分析-v2.tfl) ​

.tfl 是一个 zip 包,内含 5 个条目:

条目大小作用
flow36571 B节点与连接定义(JSON)
displaySettings9124 B画布坐标
maestroMetadata11960 B版本与特性声明
flowGraphImage.png31917 B流程截图
flowGraphThumbnail.svg2697 B缩略图

流程版本 majorVersion=1, minorVersion=4,documentId=0343c2da-b31b-4242-bd89-577ef469883e。

2.1 两个输入源 ​

输入 A:漫跑记录(LoadSql) — 主链

项值
节点类型.#########
connectionId#########
连接名##########
classmysql
server / port / dbname####### / ######### / ###########
username#############
字段数23(当时的列数,现已扩到 35)

注意:流程里记录的字段是 23 列,而库里现在是 35 列。 Prep 的 LoadSql 会在每次运行时重新探测列,所以新列能被带上; 但流程节点的 fields 列表是保存时的快照,不随库变化。

输入 B:旧格式-高一(LoadCsv) — 旁链

项值
节点类型.v1.LoadCsv
文件~/Desktop/漫跑记录分析/old/子sheets/outputs/旧格式-高一.csv
编码UTF-8,逗号分隔,含表头
字段班级、姓名、学号、2026-09-07、2026-09-08、2026-09-09
行数689

这是 9 月 7/8/9 三天的旧数据,值只有 1 或空(打卡布尔),共 513 个 1。

2.2 主链逐节点 ​

按数据流向顺序,共 8 个节点:

① 漫跑记录(输入) ​

读 漫跑库.漫跑记录,23 列(保存时)。

② 清理 1(容器,1 个节点) ​

节点类型配置
筛选器.v1.FilterOperationNOT ISNULL([姓名])

作用:丢掉没有姓名的行。库里有 689 行,此处应全部保留。

③ 转置 - 开始(SuperUnpivotExtended) ​

把「列名里带『开始』的列」转成行。

json
unpivotColumns: [{
  unpivotColumnName: "开始",
  columnInformation: {
    bindingsType: "wildcard",
    wildcardType: "Contains",
    wildcardExpression: "开始"
  }
}]
literalColumnName: "转置 1 名称"
  • bindingsType: wildcard + Contains "开始" → 新增的 …开始 列会自动进入转置,不用改流程。这是全流程最关键的设计。
  • 产出:转置 1 名称(原列名,如 2026/09/10开始)+ 开始(值)。

④ 转置 - 结束(SuperUnpivotExtended) ​

同上,Contains "结束",产出 转置 1 名称 + 结束。

③④ 并行,都以 ② 的输出为输入。

⑤ 清理 2(容器,3 个节点) ​

顺序节点配置
1只保留 开始 + 另外 4 个保留 开始, 转置 1 名称, 学号, 姓名, 班级
2Add 日期DATE(LEFT([转置 1 名称],FIND([转置 1 名称],"开始")-1))
3将 开始 重命名为 开始时间开始 → 开始时间

日期提取逻辑:FIND("开始") 定位到"开始"二字的起始位置,取它之前的全部字符, 再用 DATE() 转日期。对 2026/09/10开始,LEFT(...,FIND-1) = 2026/09/10。

⑥ 清理 3(容器,3 个节点) ​

同 ⑤,处理 结束: 保留 结束/转置 1 名称/学号/姓名/班级,FIND("结束") 提日期,结束 → 结束时间。

⑦ 联接 1(SuperJoin) ​

四键全外联接:

json
conditions: [
  {left: "[姓名]", right: "[姓名]", comparator: "=="},
  {left: "[班级]", right: "[班级]", comparator: "=="},
  {left: "[学号]", right: "[学号]", comparator: "=="},
  {left: "[日期]", right: "[日期]", comparator: "=="}
],
joinType: "full"
  • 左 = 清理2(开始侧),右 = 清理3(结束侧)
  • full = 全外:只有开始或只有结束的记录也保留(对应 1.3 节的 5 条 + 3 条)
  • 联接后自动移除 日期-1、学号-1、姓名-1、班级-1 四个重名列

这里用姓名而非仅学号做键,是本工作簿的一个隐患:689 人里 260001(高一12班)与 260002(高一15班)同名,靠班级+学号区分才安全。 当前四键组合没有产生错误,但多余的条件一旦有一个字段格式漂移(如班级空格),联接就会断。

⑧ 清理 终(容器,2 个节点) ​

顺序节点公式
1Add 打卡状态IF NOT ISNULL([开始时间]) AND NOT ISNULL([结束时间])
THEN TRUE ELSE FALSE END
2Add 持续时间IF NOT ISNULL([开始时间]) AND NOT ISNULL([结束时间])
THEN [结束时间] - [开始时间]
ELSE NULL END

口径定义在这里,不在 Tableau 里:

  • 打卡状态 = TRUE 要求开始、结束都非空;
  • 只有一边的记录 → 打卡状态 = FALSE、持续时间 = NULL。

所以 1.3 节那 8 条半截记录,在长表里是 FALSE + NULL,不会污染用时统计。

⑨ 并集 1(SuperUnion) ​

把两条链按列名并起来,namespaceFieldMappings 均为空(同名自动对齐)。 并集后移除 Table Names、转置 1 名称(但保留了 转置 1 名称-1,见 3.3)。

  • 主链:16 个日期列 × 689 人 → 11024 行(含 3 个全空日)
  • 旁链:3 天 × 689 人 → 2067 行
  • 合计 13091 行 ✔ 与 Hyper 元数据一致

⑩ 输出(WriteToHyper) ​

json
hyperOutputFile: "~/Desktop/漫跑记录分析/漫跑数据分析-2026R3.hyper"
tdsOutput:       "~/Desktop/漫跑记录分析/漫跑数据分析-2026R3.tds"

2026R3.tds 当前不存在(只有 hyper 落盘)。

2.3 旁链逐节点 ​

① 旧格式-高一(LoadCsv) ​

见 2.1 输入 B。

② 转置 5(SuperUnpivotExtended) ​

与主链不同,这里用的是字面量列表而非通配符:

json
literalColumn: {
  literals: ["2026-09-07","2026-09-08","2026-09-09"],
  literalColumnName: "转置 1 名称",
  names: ["2026-09-07","2026-09-08","2026-09-09"]
}
unpivotColumns: [{unpivotColumnName: "转置1值", manualBindings: [同上三列]}]

转置前有个注解动作:学号 改为字符串(ChangeColumnType), 以便与主链的 学号(也是字符串)对齐。

③ 清理 旧(容器,5 个节点) ​

顺序节点配置
1将 转置1值 重命名为 打卡状态转置1值 → 打卡状态
2将 打卡状态 重命名为 打卡状态-old打卡状态 → 打卡状态-old
3Add 日期DATEPARSE("yyyy-MM-dd",[转置 1 名称])
4将 打卡日期 更改为 日期日期 类型 → date
5Add 打卡状态IFNULL([打卡状态-old] = 1, FALSE)

命名有点绕:先把"值"叫成 打卡状态,再改名为 打卡状态-old,最后重新算一个布尔 打卡状态。

DATEPARSE("yyyy-MM-dd", ...) 对应旁链列名格式(短横线), 而主链用 DATE(LEFT(...)) 是因为主链列名是 2026/09/10开始(斜杠+后缀)。 两条链的日期解析方式不同,不能互相套用。

2.4 流程的 9 个节点级属性 ​

流程给 9 个节点都挂了 PrimaryKey = 学号(.v2019_1_3.PrimaryKey):

清理 1、清理 2、清理 3、清理 终、清理 旧、联接 1、并集 1、漫跑记录、转置 - 结束

这是流程内的主键声明,用于行级追踪,不影响输出列结构。

2.5 一句话总结流程 ​

把宽表按「开始」「结束」两个通配符各转一次 → 各自从列名里抠出日期 → 用四键全外联接拼回一行一天 → 算时长和打卡状态 → 再并上 9 月 7–9 的旧布尔数据 → 写长表。


三、Hyper 提取(漫跑数据分析-2026R3.hyper) ​

3.1 文件与规模 ​

项值
大小131072 B(128 KB)
标识文件头 Hyper + 版本字节
压缩lz4
关系名Extract(namespace Extract)
列数10
行数13091

3.2 十列与非空计数 ​

Hyper 元数据里 nullCounts 直接给出每列空值数,行数 13091 减一下就是非空数:

列类型空值数非空数
日期Date013091
打卡状态Bool013091
持续时间BigInt115751516
打卡状态-oldBigInt12578513
转置 1 名称-1Varchar206711024
开始时间BigInt115701521
结束时间BigInt115721519
班级Varchar013091
姓名Varchar013091
学号Varchar013091

交叉验证:

  • 持续时间 非空 = 1516 = 1.3 节"完整打卡"数 ✔
  • 开始时间 非空 = 1521 = 1516 + 5(只有开始)✔
  • 结束时间 非空 = 1519 = 1516 + 3(只有结束)✔
  • 打卡状态-old 非空 = 513 = 旁链 CSV 里 1 的个数 ✔
  • 转置 1 名称-1 非空 = 11024 = 主链行数 ✔

这五个等式成立,说明 Hyper 与源数据完全对得上,本手册后续所有数字都可放心引用。

3.3 一个容易踩的坑:转置 1 名称-1 ​

并集的两个输入,列名并不一致:

  • 主链:联接后同时存在 转置 1 名称(左=开始侧)和 转置 1 名称-1(右=结束侧); 并集前只移除了 转置 1 名称,转置 1 名称-1 被保留下来。
  • 旁链:只有 转置 1 名称(值形如 2026-09-07),并集时与主链同名不匹配,未进入结果。

所以最终 Hyper 里:

列非空行数属于哪条链值形如
转置 1 名称-111024主链(689 × 16)2026/09/10结束(据联接右侧推导)
(空)2067旁链(689 × 3)—

-1 来自联接:右表(清理 3 / 结束侧)的同名列被自动加后缀。 其值形如 2026/09/10结束 是从流程结构推导的(Hyper 内容经 lz4 压缩,未能直接读取字面值); 但"11024 条非空、恰好等于主链行数"是 nullCounts 直接给出的。

结论与直觉相反:这个字段有值的是主链,空的才是旁链。 它记录的是每次打卡的"结束侧原始列名",对分析没有直接价值, 但拖进视图会让人误以为"这个字段只有一部分行有数据"。 真正想区分两条链,应看 打卡状态-old(只有旁链有值,513 条)。

3.4 长表与宽表的行数换算 ​

13091 = 689 × 16(主链:宽表 16 个日期列全部展开)
      + 689 × 3 (旁链 9/7、9/8、9/9)

关键:转置是逐列展开,空列也产生行。 主链 16 天 × 689 = 11024 行,其中 10/08、10/09、10/12 这 3 天全空, 贡献了 3 × 689 = 2067 行,这些行的 开始/结束 为 NULL、打卡状态 为 FALSE。

于是:

说法含义对不对
"主链展开 16 天"结构事实(转置按列数展开)✔
"只有 13 天有记录"内容事实(3 天全空)✔
"13091 = 689×13 + 689×3"把两者混为一谈✘

计算"打卡天数"时,空列会因为 打卡状态=FALSE 被排除,所以不影响结果; 但计算"应跑天数""出勤率"时,用 16 还是 13 会差 19%。


四、TWB 工作簿详解(漫跑数据分析.twb) ​

4.1 文件头 ​

属性值
version18.1
original-version18.1
source-build2026.2.3 (20262.26.0912.1023)
source-platformmac
大小308235 B(301 KB)
文档特性12 项,含 ObjectModelEncapsulateLegacy、WorkbookFingerprinting 等

4.2 数据源连接 ​

工作簿只有一个数据源 federated.14ozbt31f4e6j91ahfdv914rnb5n,caption 为「输出 数据提取」。

connection class = federated
└─ named-connection name = hyper.01oaehx13d300j146rwjq00bozw8   caption = 输出
   └─ connection class = hyper
      dbname        = ~/Desktop/漫跑记录分析/漫跑数据分析-2026R3.hyper
      username      = tableau_internal_user
      authentication= auth-none
      author-locale = zh_CN
└─ relation name = Extract   table = [Extract].[Extract]   type = table

dbname 指向 漫跑数据分析-2026R3.hyper,即 Prep 流程的当前输出,链路是通的。

date-options start-of-week = monday:周从周一开始,影响 周() 类计算与日期轴分组。 semantic-values 里声明了国家为「中国」,影响地图与本地化格式。

4.3 元数据记录(10 条) ​

工作簿缓存的列定义,与 Hyper 的 10 列一一对应:

remote-nameremote-typelocal-typeapprox-count含义
日期133date1919 个不同日期(旧 3 + 主链 16)
打卡状态11boolean2true / false
转置 1 名称-1129string17主链结束侧列名
打卡状态-old20integer20 / 1(旁链)
开始时间20integer64分钟偏移
结束时间20integer56分钟偏移
持续时间20integer41用时值个数
班级129string1616 个班
姓名129string1165估算值(含重复)
学号129string1168估算值(含重复)

日期 = 19 与 Hyper 的 19 个日期一致,说明元数据已按当前提取刷新过。 approx-count 是采样估算,不是精确 COUNT(DISTINCT),不要当去重数使用。

4.4 字段清单(11 个 column 定义 + 17 个 column-instance) ​

原始字段(8 个,来自 Hyper)

字段类型角色用途
日期datedimension / ordinal时间轴、全局过滤
打卡状态booleandimension / nominal全勤判定
开始时间integermeasure出发时刻分布
结束时间integermeasure结束时刻分布
持续时间integermeasure用时分布
班级stringdimension / nominal分组
姓名stringdimension / nominal排行
学号integer(自定义为 ordinal)dimension / ordinal排行主键

计算字段(6 个)

captionname数据类型角色公式
打卡天数Calculation_0268816372953088integermeasure{FIXED [学号], [姓名], [班级] : SUM(IF [打卡状态] THEN 1 ELSE 0 END)}
结束计数Calculation_0399311583469568integermeasureCOUNTD(IF NOT ISNULL([结束时间]) THEN [学号] END)
开始计数Calculation_0399311583645697integermeasureCOUNTD(IF NOT ISNULL([开始时间]) THEN [学号] END)
周期内是否全勤?Calculation_4496984931803136booleandimension{FIXED [班级],[姓名] :
MIN(IF [打卡状态] THEN 1 ELSE 0 END)}
=1
全勤计数Calculation_4496984936570881integerdimension / ordinalINT(IF [Calculation_4496984931803136] THEN [姓名] END)
学生是否达标Calculation_4496984964349958integermeasureIF [Calculation_4496984931803136] THEN 1 ELSE 0 END

数据桶(1 个)

caption名称定义
持续时间 (数据桶)[持续时间 (数据桶)]bin on [持续时间],size = 27,peg = 0

表对象(1 个)

[__tableau_internal_object_id__].[Extract (Extract.Extract)_527E78B7B1044B948C919C36F90C704B] —— Tableau 内部的对象 id,对应 object-graph 里的 Extract 节点。

4.5 六个计算字段的口径逐条 ​

打卡天数 ​

{FIXED [学号], [姓名], [班级] : SUM(IF [打卡状态] THEN 1 ELSE 0 END)}
  • FIXED 在学号+姓名+班级这一行级粒度上算,结果是一人一个值(该人在当前日期范围内的打卡天数)。
  • 这是一个行级 LOD 结果,但字段角色被设为 measure,在工作表里又以 SUM() 聚合。 一人 N 行、每行都带同一个值,SUM 理论上会得到 N × 天数。
  • 实测当前显示正常(依赖 Tableau 对 LOD 结果的去重)。但这个行为依赖优化器, 不是公式本身保证的,换聚合方式(如 AVG)或换版本时可能出现倍数偏差。

周期内是否全勤? ​

{FIXED [班级],[姓名] :
MIN(IF [打卡状态] THEN 1 ELSE 0 END)}
=1
  • 粒度是 班级+姓名,不是学号。同班同名会合并;同名不同班(学生W)靠班级正常区分。
  • MIN(...)=1 的语义是:该人在当前日期筛选范围内从未缺勤,而不是"跑够了 N 天"。 字段名里的"全勤"容易让人误读成后者。
  • 它随全局日期过滤器一起变化:把日期范围收窄,"全勤"就变成"在这个范围内没缺"。

全勤计数 ​

INT(IF [Calculation_4496984931803136] THEN [姓名] END)

INT() 作用于中文姓名,结果恒为 NULL。这个字段在数学上没有意义。 它只出现在两个窗口的 color-one-way 高亮列表里,没有被任何工作表使用。

学生是否达标 ​

IF [Calculation_4496984931803136] THEN 1 ELSE 0 END

周期内是否全勤? 的 0/1 版本,供 SUM() 求"全勤人数"。语义随上一节。

开始计数 / 结束计数 ​

COUNTD(IF NOT ISNULL([开始时间]) THEN [学号] END)
COUNTD(IF NOT ISNULL([结束时间]) THEN [学号] END)

去重学号计数,即"有开始 / 有结束的人数"。一人可能跑多次,所以结果必然小于记录数。 当前工作簿未使用这两个字段,只有数据源里挂着实例(usr: 派生)。

4.6 实例(column-instance)全表 ​

实例(column-instance)是"字段 + 聚合方式"的组合,Tableau 用它把同一个字段 以不同粒度重复使用(如 学号 既可以做维度、也可以 COUNT)。

数据源级共声明 17 个,被工作表实际引用的 13 个:

实例派生用于
[none:日期:qk]None全部 6 张表(全局过滤 + 切片)
[none:班级:nk]None基础表格×2、全勤排名、月汇总班级排名
[none:姓名:nk]None基础表格-弃用、全勤排名
[none:学号:ok]None全勤排名(行架第一层)
[none:开始时间:qk]None打卡同学压力
[none:结束时间:qk]None打卡同学压力
[none:持续时间:qk]None平均跑步时间
[ctd:学号:qk]CountD平均跑步时间(人数)
[cnt:学号:qk]Count打卡同学压力(人数)
[sum:持续时间:qk]Sum平均跑步时间(颜色)
[sum:Calculation_0268816372953088:qk]Sum全勤排名、月汇总班级排名
[sum:Calculation_4496984964349958:qk]Sum基础表格-数据(全勤人数)
[none:Calculation_4496984931803136:nk]None基础表格×2(全勤真假)

数据源级声明但从未被任何工作表引用的(4 个,冗余):

[__tableau_internal_object_id__].[cnt:Extract...]、[attr:学号:ok]、[attr:持续时间:qk]、 [cnt:持续时间:qk]、[ctd:持续时间:qk]

另有若干实例(如 [none:Calculation_0268816372953088:qk]、[sum:Calculation_0268816372953088:qk]) 只出现在各表的内联声明里,数据源级没有 —— 这是 Tableau 的正常行为,不算问题。

4.7 全局筛选器(唯一一条,数据源级) ​

工作簿里有一条共享(数据源级)筛选器,定义在 <shared-views> 里:

xml
<shared-view name='federated.14ozbt31f4e6j91ahfdv914rnb5n'>
  <datasources>
    <datasource caption='输出 数据提取' name='federated.14ozbt31f4e6j91ahfdv914rnb5n' />
  </datasources>
  <datasource-dependencies datasource='federated.14ozbt31f4e6j91ahfdv914rnb5n'>
    <column-instance column='[日期]' derivation='None' name='[none:日期:qk]'
                     pivot='key' type='quantitative' />
    <column datatype='date' name='[日期]' role='dimension' type='ordinal' />
  </datasource-dependencies>
  <filter class='quantitative'
          column='[federated.14ozbt31f4e6j91ahfdv914rnb5n].[none:日期:qk]'
          context='true'
          included-values='in-range-or-null'>
    <min>#2026-10-15#</min>
    <max>#2026-10-15#</max>
    <target field='[federated.14ozbt31f4e6j91ahfdv914rnb5n].[none:日期:qk]' />
  </filter>
</shared-view>

它的作用(本手册只关心"在系统里起什么作用") ​

属性含义
<shared-view>数据源级共享,6 张工作表全部受它约束,不需要每张表各放一个
column = [none:日期:qk]作用对象是 日期 字段
context='true'上下文筛选器:先于该表内所有其他筛选器执行。表内的 打卡天数 0–15、班级 筛选都在它之后
included-values='in-range-or-null'区间加空值:日期为空的行也会通过,不会被误删
<min>/<max>保留的日期闭区间

一句话:这是整个工作簿的"时间窗口开关"。改这一处,所有图表的统计范围同时变, 不需要逐表调整;它先算完,表内筛选再在其结果上叠加。

这个区间填什么,只影响"当前看哪一段"。工作簿本身不依赖某个固定区间, 换周期时改 min/max 即可,图表结构不变。

表内还有两个筛选器(非全局,只影响单表) ​

工作表筛选器作用
全勤排名打卡天数 in-range 0–15,context=true只列该区间内的人;0–15 覆盖当前全部取值,等于暂时不过滤
全勤排名班级 level-members,context=true班级多选,默认全选
基础表格-弃用、基础表格-数据周期内是否全勤? = true只保留"范围内零缺勤"的人
平均跑步时间持续时间 非空排除没有完整记录的行
打卡同学压力开始时间 非空、结束时间 非空排除半截记录
月汇总班级排名无只受全局日期筛选

全勤排名 的两条筛选器都带 context=true,与全局日期筛选一样属于上下文级, 在表内先执行。

4.8 六张工作表逐张 ​

表 1:全勤排名 ​

项值
行学号 / (姓名 / 班级) 三层
列SUM(打卡天数)
标记Automatic
编码文本 = SUM(打卡天数)
筛选器打卡天数 0–15(context)、班级 全选(context)
排序学号 按 SUM(打卡天数) 降序
切片打卡天数、日期、班级
simple-id{D40C7CD0-1773-4D18-94C7-69FD1143C52D}

水平条形排行:每人一条,长度 = 打卡天数,按天数降序。 行架带 学号 是为了让同名不同人(学生W)分开显示,这是对的做法。

打卡天数 0–15 这个范围当前覆盖所有可能取值(最大 16,但极少), 等于不筛选;它留在这里是为了以后收窄范围时直接改。

表 2:基础表格-弃用 ​

项值
标题全勤人数
行班级 / 姓名(两层)
列周期内是否全勤?
标记Automatic
编码颜色 = 周期内是否全勤?
筛选器周期内是否全勤? = true
切片日期、周期内是否全勤?
样式该列宽 121;标记标签关闭
simple-id{96E5E0F0-4FF3-4C79-A90D-7F638AB8C5D7}

每个全勤学生一个点,按班级分行,列上是 true/false。 因为过滤到 true,false 列是空的。表名自带"弃用",是早期草稿。

表 3:基础表格-数据 ​

项值
行班级
列无(单列文本表)
标记Automatic
编码文本 = SUM(学生是否达标)
筛选器周期内是否全勤? = true
切片日期、周期内是否全勤?
标记标签15 条显式标签,全部 label-state=on
simple-id{22336AFF-BB8B-4497-835A-E871BF7A93D9}

这是"各班全勤人数"表,也是唯一带 maximized=true 的窗口(打开即最大化); 但 15 条标签是冻死在 XML 里的字面量:

班级标签值班级标签值
高一 1 班14高一 9 班6
高一 2 班6高一 10 班7
高一 3 班11高一 11 班11
高一 4 班18高一 12 班5
高一 5 班5高一 13 班4
高一 6 班3高一 14 班6
(高一 7 班 缺失)—高一 15 班6
高一 8 班10高一 16 班3

合计 115。这些数字不会随数据刷新而变。 缺高一 7 班是因为该班最高打卡天数只有 5,0 人全勤,Tableau 未生成标签。

表 4:平均跑步时间 ​

项值
行COUNTD(学号)(人数)
列持续时间
标记Area(面积图)
编码颜色 = SUM(持续时间)(蓝色渐变 blue_10_0)
文本 = COUNTD(学号)
LOD = COUNTD(学号)
筛选器持续时间 非空
切片日期、持续时间
样式轴高 57;颜色插值
simple-id{4F41E126-1F31-4624-A24B-E9B8B1370587}

表名与内容不符:这不是"平均用时",而是"用时分布"—— 横轴是用时,纵轴是"该用时上有多少人次",面积高度代表分布的密集程度。 "平均"这个词只在颜色编码 SUM(持续时间) 上勉强沾边。

表 5:打卡同学压力 ​

项值
行COUNT(学号)(人次),轴标题「人数」
列(开始时间 + 结束时间) 双轴合并
标记Area(3 个 pane)
编码颜色 = [:Measure Names](两条线区分)
文本 = COUNT(学号)
筛选器开始时间 非空、结束时间 非空
切片日期、开始时间、结束时间
趋势线仅 pane 8(开始时间轴)有:线性拟合,无置信带
样式结束时间轴 fold=true(双轴折叠)
simple-id{B140757F-5C37-4597-8794-737D5F769FAE}

三个 pane:

  • pane 7:无 x 轴名(基线)
  • pane 8:x = 开始时间,带趋势线
  • pane 9:x = 结束时间

图的语义:横轴是分钟偏移(见 1.5),纵轴是"在这一点出发/结束的人次", 两条面积带分别是"出发时刻人数"与"结束时刻人数"。 "压力"指的是人流在时间上的集中程度。

表 6:月汇总班级排名 ​

项值
行班级
列SUM(打卡天数)(轴标题「打卡人次」)
标记Automatic
编码文本 = SUM(打卡天数)
筛选器无(仅受全局日期筛选)
排序班级 按 SUM(打卡天数) 降序
切片日期
simple-id{ED323CB1-6E83-467A-A7A1-DA79FED6572A}

班级维度的打卡人次排行。轴标题写"人次"、字段叫"打卡天数", 实际是 SUM(FIXED 天数),即"全班所有人的打卡天数之和",用"人次"更贴切。

4.9 仪表板 1 ​

项值
尺寸1000 × 800(min = max)
zones0 个(空)
手机版式自动生成(auto-generated=true),700 高,vscroll
simple-id{59A2FA23-3134-4302-91F6-C4C5E8F1A322}

仪表板是空壳,6 张工作表都没有拖进去。

4.10 窗口(windows)配置要点 ​

窗口关键配置
基础表格-弃用左侧卡:颜色、日期筛选;高亮 6 个字段
基础表格-数据maximized=true;左侧卡:全勤筛选、日期筛选
全勤排名右侧卡:天数筛选、班级筛选、日期筛选
平均跑步时间右侧卡:日期筛选、颜色
打卡同学压力右侧卡:日期筛选、颜色(Measure Names)
月汇总班级排名右侧卡:日期筛选
仪表板 1active id=-1(无活动部件)

工作簿共 15 个 tuple-reference,全部属于 基础表格-数据 的 15 条标记标签。 没有任何持久化的选中记录(selection-collection 为空), 所以打开工作簿不会出现"莫名只有几个人被高亮"的情况。

五、图表语义速查(别被表名骗了) ​

工作表名实际画的是一句话读法
全勤排名每人的打卡天数排行条形长度 = 天数,按天数降序;行架带学号以区分同名
基础表格-弃用全勤学生名单矩阵每行一个全勤学生,按班级分组;已弃用
基础表格-数据各班全勤人数(单列表)数字是冻结的旧值,不是实时值
平均跑步时间用时分布(面积图)横轴用时、纵轴人次;不是平均值
打卡同学压力出发时刻 + 结束时刻的人次曲线双轴叠加,看人流集中在哪个分钟段
月汇总班级排名各班打卡人次总和条形长度 = 全班天数之和

四条常见误读 ​

  1. 把 平均跑步时间 当平均值 → 它没有算任何平均数,是把记录按用时值铺开数人头。 想要平均值应看 1.6 节的 8.55,或新建 AVG([持续时间])。

  2. 把 全勤排名 的条形长度当"跑了几次" → 是"打卡天数", 口径为 FIXED 学号+姓名+班级 下的 SUM(IF 打卡状态 THEN 1), 即该人在当前日期筛选范围内有多少天满足 打卡状态=TRUE。改日期范围,条形就变。

  3. 把 基础表格-数据 的 115 当当前值 → 是建立工作簿时的快照,且缺高一 7 班。 刷新数据不会更新这些数字(它们是 mark-label 字面量,不是计算)。

  4. 把"全勤"理解为"跑够了 N 天" → 周期内是否全勤? 的定义是 "在筛选范围内从未缺勤"(MIN(打卡) = 1)。范围内只要有任意一天缺, 就判为非全勤,与该人总共跑了多少天无关。


六、已知问题清单(仅记录,不含修复) ​

按严重度排列。"定位"给到文件内可检索的锚点。

P0 — 数据不一致 ​

Q1. 数据库落后于 Excel:10/13 的 57 条记录没进库

  • 现象:Excel 的 2026/10/13 列有 57 条完整记录(开始、结束都非空), 数据库同名列 0 条(实测 SELECT COUNT(*) ... WHERE 开始 IS NOT NULL AND 结束 IS NOT NULL)。
  • 影响链条:Prep 用 LoadSql 读库 → Hyper 只有 1516 条(Excel 是 1573)→ 工作簿、仪表板、所有导出都缺这 57 条。
  • 影响范围:主要是 10/13 一天;其余 15 天的完整记录三边一致。
  • 另有 1 处反向差异:库内 260001(学生D)在 10/15 有"只有开始、没有结束"的半截记录, 而 Excel 该格为空。因此库内半截 6 条 / Excel 半截 5 条。
  • 合并后:库内完整 1516 + 半截 6 = 1522 条有值;Excel 完整 1573 + 半截 5 = 1578 条有值。
  • 定位:
    • Excel:2026/10/13开始 / 2026/10/13结束 两列(AD、AE 列)
    • 库:漫跑记录 同名列
  • 该问题属"入库同步"范畴,本手册只记录现象,处理办法见 ../漫跑数据入库/数据库操作手册.md 第四节。
  • 附带后果:本手册所有"库内 / Hyper"口径的数字都比"Excel"口径少 57 条, 两套数字并列给出,避免混用。

P1 — 口径与呈现 ​

Q2. 基础表格-数据 的 15 个数字是冻结字面量

  • 现象:mark-labels/mark-label 里 15 条 label-state=on,值是写死的 3–18。
  • 缺高一 7 班:该班最高打卡天数只有 5,0 人全勤,Tableau 未生成标签,属正常。
  • 合计 115。这个数无法用当前任何日期范围复现,说明来自更早的数据版本。
  • 定位:worksheets/worksheet[@name='基础表格-数据']/table/mark-labels
  • 影响:图上数字与当前筛选结果不一致,且刷新不更新(字面量,不是计算)。

Q3. 半截记录在两种口径下结论相反

  • 当前口径(流程 清理 终 与工作簿 打卡天数 一致):开始 且 结束都非空才算打卡。
  • 半截记录共 6 条(库内),涉及 6 人: 学生A、学生E、学生C、学生F、学生B、学生D。 它们在长表里是 打卡状态=FALSE、持续时间=NULL, 不计入打卡天数、也不进用时分布。
  • 若改用"任一非空"口径,这些人各会多算 1 天,但也不会进用时统计(时长仍算不出来)。
  • 定位:flow → 清理 终/Add 打卡状态;datasources/datasource/column[@caption='打卡天数']
  • 说明:本手册只记录现象,不判定哪个口径正确。

Q4. 打卡天数 的 LOD + SUM 组合存在倍数风险

  • FIXED 产出人级常量,SUM() 又在行级求和,理论上应为 行数 × 天数。
  • 实测当前显示正常(依赖 Tableau 对 LOD 结果的去重)。
  • 定位:datasources/datasource/column[@caption='打卡天数']
  • 影响:换聚合方式(AVG/MIN)或换 Tableau 版本时可能出现倍数偏差。

Q5. 周期内是否全勤? 用姓名而非学号做键

  • {FIXED [班级],[姓名] : ...}:同班同名会合并。
  • 本数据集无同班同名,但 学生W 两人跨班,靠班级才区分开。
  • 语义是"范围内零缺勤",与字段名"全勤"易混淆。
  • 定位:datasources/datasource/column[@caption='周期内是否全勤?']

Q6. 全勤计数 是无效字段

  • INT(IF ... THEN [姓名] END):INT() 作用于字符串姓名,恒为 NULL。
  • 仅在两个窗口的 color-one-way 里出现,无工作表使用。
  • 定位:datasources/datasource/column[@caption='全勤计数']

Q7. 数据源级有 4 个冗余 column-instance

  • [__tableau_internal_object_id__].[cnt:Extract...]、[attr:学号:ok]、 [attr:持续时间:qk]、[cnt:持续时间:qk]、[ctd:持续时间:qk]
  • 早期探索遗留,未被任何工作表引用。
  • 定位:datasources/datasource/column-instance

Q8. 仪表板 1 为空

  • zones 子元素数 = 0,6 张工作表均未上板。
  • 定位:dashboards/dashboard[@name='仪表板 1']/zones

P2 — 命名与呈现 ​

Q9. 平均跑步时间 名实不符

  • 实为用时分布面积图,无任何平均计算。
  • 定位:worksheets/worksheet[@name='平均跑步时间']

Q10. 打卡同学压力 的三 pane 只有一条趋势线

  • 仅 pane 8(开始时间)有 trendline fit=linear;pane 9(结束时间)没有。
  • 两条面积带放在一起但只有一条有拟合,视觉上会误导。
  • 另:13–60 的分钟偏移口径未在任何地方定义(见 1.5)。
  • 定位:worksheets/worksheet[@name='打卡同学压力']/table/panes/pane[@id='8']/trendline

Q11. 基础表格-弃用 仍留在工作簿内

  • 已明确弃用但未删除,参与缩略图与窗口列表。
  • 定位:worksheets/worksheet[@name='基础表格-弃用']

Q12. 全勤排名 的天数筛选范围 0–15 依赖"库内缺 10/13"这一前提

  • 当前连接的数据(库内,19 天口径)最大打卡天数是 15,所以 0–15 完整覆盖、暂时不丢人。
  • 但 Excel 全量口径下最大是 16(有 20 人),因为 10/13 那 57 条没进库(见 Q1)。
  • 也就是说:一旦 Q1 被修好、10/13 入库,0–15 就会漏掉这 20 人。
  • 该筛选是 context=true,在表内最先执行。
  • 定位:worksheets/worksheet[@name='全勤排名']/table/view/filter
  • 说明:本手册只记录该联动关系,不评价取值是否合适。

Q13. 流程与成品的字段快照不一致

  • 流程 LoadSql 记录 23 列,库里 35 列;流程 LoadCsv 指向 old/子sheets/outputs/, 属历史目录。
  • 定位:flow → nodes['626aa6a3-...'].fields、connections['b70de1f7-...']

七、每周更新对可视化的影响(现状描述) ​

只描述"会发生什么",不给改法。

变化Prep 流程Hyper工作簿
宽表新增日期列通配符转置自动纳入,无需改流程重跑后日期数增加元数据需刷新;否则 approx-count 与列名会滞后
宽表新增学生LoadSql 自动带上行数增加姓名/班级维度自动出现
某日期列整列为空仍产生 689 行(值为 NULL)行数增加、该日 打卡状态=false全局筛选器决定是否可见
新增月份数据需确认流程已重跑日期数从 19 增长把全局筛选器的区间向前延一段即可看到
删除学生自动消失行数减少排行表中该人自动消失

关键点:Prep 侧靠通配符自动适应,工作簿侧靠全局筛选器的区间控制时间窗。 两者配合得当,加数据不需要改结构;需要改的只是筛选器的 min/max。

另外两处会滞后、但不影响正确性的:

  • 基础表格-数据 的 15 条标记标签(字面量,不随刷新变化,见 Q2)
  • 全勤排名 的 打卡天数 0–15 范围(现有数据最大 15,暂时够用;见 Q12)

八、排查速查 ​

现象先查锚点
图上完全没有某段日期全局日期筛选器的区间shared-views/shared-view/filter 的 min/max
班级数字不对且刷新无效标记标签字面量worksheet[@name='基础表格-数据']/table/mark-labels
排行表少了高分学生打卡天数 的 in-range 上界worksheet[@name='全勤排名']/table/view/filter
某个人在自己表里查不到班级 筛选器是否全选worksheet[@name='全勤排名']/table/view/filter
重名字段报错或列错位元数据 转置 1 名称-1 与 Hyper 是否一致metadata-records/metadata-record
用时分布图横轴超出数据范围数据桶 size=27column[@name='[持续时间 (数据桶)]']
全勤人数与手算不符全勤口径(A/B/C/D 四种)本手册 1.7
时间段落在 13–60分钟偏移口径本手册 1.5
打卡数比 Excel 少 16库与 Excel 是否同步本手册 Q1

附录 A:文件清单 ​

文件大小角色
../漫跑数据分析.xlsx110758 B源宽表(689 × 35)
../漫跑数据分析-v2.tfl37199 BPrep 流程(13 节点)
../漫跑数据分析-2026R3.hyper131072 B长表提取(13091 × 10),工作簿当前连接它
../漫跑数据分析.twb308235 B工作簿(6 表 + 1 空仪表板)
../old/子sheets/outputs/旧格式-高一.csv25252 B旁链输入(9/7–9/9,689 行)
../old/输出.hyper131072 B13 天版提取(8957 行),历史产物
../old/输出-final.hyper131072 B更早的实验版本(9 列,含 转置1值)
../old/temp-输出.hyper65536 B更早的实验版本(8 列,含 Table Names)
../old/工作簿 2.twb—更早的工作簿
../宣传/月汇总全勤排名_摘要.csv—排行导出(UTF-16,96 行 + 表头)

附录 B:Prep 节点 ID 对照 ​

节点类型ID(前 8 位)
漫跑记录LoadSql626aa6a3
旧格式-高一LoadCsv0d021b59
清理 1Container7ce6e24d
清理 2Container3f7e7929
清理 3Containercbae1f49
清理 旧Containera67ba473
清理 终Container248c103a
转置 - 开始SuperUnpivotExtendeda528a1ad
转置 - 结束SuperUnpivotExtendeddb0b8b84
转置 5SuperUnpivotExtended76a7653a
联接 1SuperJoin5887b0d8
并集 1SuperUnion48ed450a
输出WriteToHyper4706da33

容器内部节点 ID(按执行顺序):

容器内部节点(ID 前 8 位)
清理 1833d119e 筛选器
清理 2206e7a88 保列 → ec6d6cc7 Add 日期 → 73f926d1 改名
清理 38d31f566 保列 → 4727894b Add 日期 → f680bf7b 改名
清理 旧80a56f6f 改名 → 0563f2ca 改名 → 558ec9d3 Add 日期 → 2c76a51b 改类型 → 0343d335 Add 打卡状态
清理 终de45c31e Add 打卡状态 → 2c0e0ce6 Add 持续时间

附录 C:关键 XML 片段 ​

全局筛选器(min/max 是当前值,换周期时改这两个日期即可)

xml
<shared-view name='federated.14ozbt31f4e6j91ahfdv914rnb5n'>
  <datasources>
    <datasource caption='输出 数据提取' name='federated.14ozbt31f4e6j91ahfdv914rnb5n' />
  </datasources>
  <datasource-dependencies datasource='federated.14ozbt31f4e6j91ahfdv914rnb5n'>
    <column-instance column='[日期]' derivation='None' name='[none:日期:qk]'
                     pivot='key' type='quantitative' />
    <column datatype='date' name='[日期]' role='dimension' type='ordinal' />
  </datasource-dependencies>
  <filter class='quantitative'
          column='[federated.14ozbt31f4e6j91ahfdv914rnb5n].[none:日期:qk]'
          context='true'
          included-values='in-range-or-null'>
    <min>#2026-10-15#</min>
    <max>#2026-10-15#</max>
    <target field='[federated.14ozbt31f4e6j91ahfdv914rnb5n].[none:日期:qk]' />
  </filter>
</shared-view>

shared-view 里同时声明了 [none:日期:qk] 这个实例和 [日期] 字段本身, 说明这条筛选器只依赖 日期 一个字段,与任何工作表无关,因此对所有表生效。

表内筛选器(全勤排名)

xml
<filter class='quantitative'
        column='[...].[none:Calculation_0268816372953088:qk]'
        context='true' included-values='in-range'>
  <min>0</min>
  <max>15</max>
</filter>
<filter class='categorical'
        column='[...].[none:班级:nk]'
        context='true'>
  <groupfilter function='level-members' level='[none:班级:nk]' />
</filter>

两条都带 context='true',与全局日期筛选器同级,先于表内其他计算执行。

数据桶

xml
<column aggregation='None' datatype='integer'
        name='[持续时间 (数据桶)]' role='dimension' type='quantitative'>
  <calculation class='bin' decimals='1' formula='[持续时间]' peg='0' size='27' />
</column>

标记标签(示例,共 15 条)

xml
<mark-label id='0' label-state='on'>
  <tuple-reference>
    <tuple-descriptor>...</tuple-descriptor>
    <tuple>
      <value>&quot;高一 4 班&quot;</value>
      <value>18</value>
    </tuple>
  </tuple-reference>
</mark-label>

附录 D:本手册数字的来源 ​

数字来源校验方式
689 人Excel 去重学号与 SHOW COLUMNS/COUNT(*) 一致
16 天 / 32 列Excel 表头与库 35 列 − 3 一致
1573 / 1516 完整打卡Excel 逐格复算 / 库与 HyperExcel 1573,库与 2026R3.hyper 均 1516,差 57(10/13)
5 / 3 半截(Excel) / 6(库内)逐格复算Excel:开始独有 5、结束独有 3;库内开始独有 6(多 260001@10/15)
13091 行Hyper nullCounts 反推= 689×16 + 689×3
8957 行(13 天版)old/输出.hyper 的 nullCounts= 689×13,且 日期 非空数与日期集合一致
1356 / 1361 / 13599 月口径复算与 old/输出.hyper 的 持续/开始/结束 非空数逐项相等
13 个有记录日(Excel) / 12 个(库内)按日计数Excel 空日 = 10/08、10/09、10/12;库内再多一个 10/13
用时 1–35,均值 8.55Excel 逐条计算中位 7,众数 5(323 条),33 个不同值
57 人 9 月全勤Excel 逐人判定口径 A
46 人 13 天全勤Excel + 旁链 CSV口径 B
115(冻结标签)TWB mark-label 字面量15 条求和
256 人有任意记录 / 254 人有完整记录Excel 逐人判定37.2% / 36.9%;差 2 人是只有半截记录的 学生A、学生B

关于半截记录:Excel 口径下共 5 条"只有开始"+ 3 条"只有结束",分布在 5 人身上:

  • 学生A:0 条完整 + 1 条半截(唯一记录就是半截)
  • 学生B:0 条完整 + 2 条半截(唯一记录都是半截)
  • 学生E:8 条完整 + 1 条半截
  • 学生C:1 条完整 + 2 条半截
  • 学生F:1 条完整 + 2 条半截

按"开始且结束"计为未打卡,半截都不计入打卡天数、也不进用时分布。 库内另有 学生D 在 10/15 多一条半截,Excel 无此数据(见 Q1)。

全部数字均为本手册写作时对现存文件的独立复算。若 漫跑数据分析.twb 或 Hyper 后续被改动,第四、五、六章的结构描述需按改动后的文件重新核对。

基于静态解析与独立复算整理