CEO 看板 — pandas、缓存,和一条「原始数据 → 指标」的流水线¶
这一章讲一件事: 把一个「给老板看的数字看板」从零建出来——读 100 万行销售数据、 让用户随意切片、算出四个关键指标、画出趋势图和占比图。 读完你掌握了数据类 Streamlit 应用的完整骨架,以及 pandas 最常用的那套动作。 这是全书从「玩具」跨到「真实应用」的第一章,后面第 07–08 章都建立在这副骨架上。
1. 这一章讲什么¶
场景是虚构电商 Note n' Nib:新 CEO 上任,问销售 VP 在哪看最新销售数字, VP 从公文包里掏出一份两个月前的纸质报告。一小时后,你接到了做 「那种带折线图的 fancy 指标页面」的任务1。
概念一句话:让高管查看销售数据的各种切面、跟踪关键指标来做决策2。 CEO 关心的四个数:总销售额(total sales)、毛利(gross margin)、 利润率(margin %)、平均客单价(ATV,average transaction value)3。 范围外:不下钻到具体行、不做预测、不解释指标为什么变化—— 第一版只看数,不分析4。
2. 顶层全景:数据的流水线¶
sales_data.csv(103.5 万行 × 10 列,90+ MB)
│ ① 读入 pd.read_csv → dataframe(内存里的表)
▼
② 清洗/准备 列名整容(Age group 取代 age_group)、加 Day 列(日期型)
│ ← 整个 prep_data 被 @st.cache_data 包住,一天内只跑一次
▼
③ 全局过滤 过滤面板(6 个 multiselect)+ 日期范围 → main_df
│
▼
④ 三种呈现 指标条(4 个 Metric 算在 main_df 上)
折线图(按 Day 分组聚合,Plotly)
环形图(按所选维度分组聚合,Plotly)
图说:③ 是分水岭——过滤之后的 main_df 是后面所有部件的唯一输入;
这也是设计图(figure 6.3)里「过滤先行」的理由:全局过滤影响所有部件。
这份数据长什么样,先建立直觉。每一行 = 某天 × 某产品 × 某细分 × 某类目 × 某性别 × 某年龄段 × 某州 的一种组合,带三个数:销售额、毛利、交易笔数。 书里给的示例行:2019-01-01,加州 18–25 岁男性买 InkStream 钢笔,8 笔交易, $134.91 销售额,$83.91 毛利5。主键(唯一能定位一行的列组合)就是 日期 + 产品 + 细分 + 类目 + 性别 + 年龄段 + 州6。
3. 核心原理(一):pandas——把 CSV 变成内存里的表¶
pandas 是 Python 数据处理的事实标准,核心概念是 dataframe(数据框): 二维表格,有行有列,每列一种数据类型;可以把它想成 Excel 表在内存里的样子7。
>>> import pandas as pd
>>> df = pd.read_csv('sales_data.csv') # 一个函数,CSV → dataframe
>>> df.info() # 体检报告
RangeIndex: 1035000 entries, 0 to 1034999
Data columns (total 10 columns):
date / product_name / segment / category / gender / age_group / state ← object(≈字符串)× 7
sales / gross_margin / transactions ← float64 × 3
memory usage: 79.0+ MB
三个马上要用的基本功:
- 选列:
df[['date','product_name']]用方括号传列表,得到子表;df['Sales']传单个名字,得到 series(序列)——可以理解成「只有一列的 dataframe」8。 - 看头几行:
df.head(5)返回前 5 行,验证数据没读错9。 - 类型:pandas 用自己的 dtype(源于 numpy),
object近似字符串、float64是 双精度浮点;read_csv(..., dtype_backend='pyarrow')可换更快的 Arrow 后端10。
文件路径一个小坑:Python 的相对路径相对于「你运行命令的目录」,不是「代码文件所在目录」。
书里用标准解法——Path(__file__).resolve().parent 取到本文件所在目录,
再用 / 拼接出 CSV 路径(/ 对 Path 对象是「拼接」不是除法,Windows 也正确)11。
st.write 是 Streamlit 的瑞士军刀:传什么显示什么——字符串、字典、
甚至 dataframe,直接渲染成表格12。开发期 st.write(data.head(5)) 就是最小验证。
4. 核心原理(二):st.cache_data——正式版缓存,和第 05 章的草根版对比¶
90 MB 的 CSV,读一次好几秒;执行模型又规定任何交互都重跑脚本——不处理的话, 用户拖一下滑块就重读一遍文件,看板根本没法用13。
第 05 章用 session_state 缓存过 API 结果,但书里指出它有两个漏洞: 刷新页面就重新加载(用户常开多个标签页,每个都要等); 每个用户各自读一遍,哪怕数据完全相同14。
st.cache_data 是正式答案:一个装饰器(decorator,贴在函数上一行、
给函数加新能力的语法),贴上去之后,这个函数同样参数第二次调用时直接返回上次的结果,
函数体不再执行15:
@st.cache_data(show_spinner="Reading sales data...", ttl="1d")
def prep_data():
df = clean_column_names(load_data()) # 读 CSV + 列名整容
df['Day'] = pd.to_datetime(df['Date']) # 日期列从字符串转日期型
return df
三个参数各有用武之地:ttl(time-to-live,存活时间)给缓存设过期—— 假设工程团队每天覆盖一次 CSV,ttl="1d" 保证数据至多滞后一天; show_spinner 把默认的「Running load_data()」换成用户看得懂的提示; 返回值是全应用共享的——缓存一次,所有用户受益(这正是我们要的)16。
一个开发期的大坑:改了被缓存的函数,刷新页面看不到效果——缓存没过期。 要么重启服务器(硬重置),要么等 TTL17。
5. 核心原理(三):过滤面板——multiselect + 布尔索引¶
界面侧:st.expander("Filters") 是可折叠容器(默认收起,不抢视线),
里面 6 个 st.multiselect(多选下拉,返回选中的列表)并排,每列一个维度18。
选项从哪来?绝不硬编码——df['Age group'].unique() 从数据本身取唯一值;
数据里来了新产品,下拉自动多一项19。
面板返回一个字典 {维度名: 选中的值列表},核心过滤一行:
def apply_filters(df, filters):
for col, values in filters.items():
if values: # 没选 = 不过滤这个维度
df = df[df[col].isin(values)]
return df
df[col].isin(values) 生成一个「每行一个 True/False」的布尔序列;
df[布尔序列] 叫布尔索引——返回 True 的那些行。这是 pandas 最高频的句式,
向量化计算(整列一次算,不写循环)也是 pandas 快的来源20。
日期范围同理:st.date_input 两个(起/止,默认值一个设为 30 天前、一个设为最新日期;
静态数据集所以把「最新日期」硬编码成 2024-08-31 保证可复现)21。
过滤用两个条件的合取:df[(df['Day'] >= start) & (df['Day'] <= end)]——
& 是逐元素 AND,括号不能省22。
布局上两个顺手修理:st.set_page_config(layout='wide') 把默认的「居中窄条」
换成全宽(必须是第一个 Streamlit 命令)23;列名在 prep_data 里统一整容
(age_group → Age group),让界面不出现技术名词24。
6. 核心原理(四):指标即配置——Metric 数据类的复用设计¶
四个指标怎么算,书里的定义(照原文):总销售额 = Sales 列求和;毛利 = Gross margin 列求和; 利润率 = 毛利 ÷ 总销售;客单价 = 总销售 ÷ 总笔数25。手算例子:
三行数据:Sales 500/300/200,Gross margin 200/120/80,Transactions 10/5/8
Total sales = 500+300+200 = $1000
Gross margin = 200+120+80 = $400
Margin % = 400/1000 = 40%
ATV = 1000/23 = $43.48
(照原书数值26。)跟上上章的 unit_config 同一个套路,把它升级成「配置驱动」:
@dataclass
class Metric:
title: str
func: callable # 一个「吃 dataframe、吐数字」的函数
type: str # "dollars" / "percent",管格式化
metrics = {
"Total sales": Metric(title="Total sales", func=lambda df: df["Sales"].sum(), type="dollars"),
"Margin %": Metric(title="Margin %", func=margin_percent, type="percent"),
# …
}
display_metrics = ["Total sales", "Gross margin", "Margin %", "ATV"] # 管顺序和取舍
func 里可以塞 lambda 也可以塞具名函数;两个比率指标必须先求和再相除
(不能对每行的比率取平均),且要防除零:... if total_sales > 0 else 027。
这个设计的收益马上兑现:折线图、指标条、环形图全从 metrics 字典取函数,
新增第五个指标 = 配置里加四行,三处图表自动跟上。
显示端:humanize.metric(25125367) → "25.1M"(大数字缩写,
给 CEO 看 $25.1m 而不是 $25,125,367,降低认知负担)28。
指标条用 st.container(border=True) 套个带边框的盒子,
里面四列各放一个 st.metric;还有个布局 hack:列内再切三列 [1,3,1]、
只往中列放内容,实现「无 HTML 的居中」29。
main_df.empty 时显示 st.warning("No data to display")——过滤到空集是常态,
不该崩30。
7. 核心原理(五):可视化——groupby 聚合 + Plotly 出图¶
时间序列 = 把任意切片聚合到「天」粒度。 groupby 把 100 万行按 Day 分桶, 对每桶应用 metric.func 求和,reset_index 整理回表格:
def get_metric_time_series(df, metric):
grouped = df.groupby('Day')
data = grouped.apply(metric.func, include_groups=False).reset_index()
data.columns = ['Day', 'Value']
return data
include_groups=False 是个细节:不让函数顺带操作分组键本身,不加 pandas 会抱怨31。
走一遍数:State=CA 的切片里 2024-08-01 有 RoyalQuill 1500 + GripLink 1300 两行,
聚合后这一天 = 280032。
Plotly 出图三步:fig = go.Figure() 空图 → fig.add_trace(go.Scatter(x=天, y=值, mode='lines+markers'))
画折线(Scatter 是散点,连上线就是折线)→ st.plotly_chart(fig, use_container_width=True)
渲染33。st.plotly_chart 白送一堆交互:缩放、悬停提示、全屏、下载图片34。
st.tabs 代替 radio 选指标——书里特意说明:切 tab 不触发重跑(radio 会), 代价是四个指标的图要在初次加载时一次性全建出来35。这是用「加载时间」换 「切换顺滑」的典型权衡。
环形图(go.Pie,hole=0.4)按所选维度聚合,同一个 groupby 套路换个分组键36。 但有一个只留两个指标的理由值得记住:Margin % 和 ATV 是比率, 把比率的切片直接占比,加起来不是 100%——占比图数学上不成立。 ATV 的正确拆法是「男性的 sales 总和 ÷ 男性的 transactions 总和」, 不是把每行 ATV 平均37。书里顺带承认了饼图的业界争议(切片大小难比较, 条形图更好),但保留它,因为「整体由部分组成」的观感直观38。
最终布局:指标条置顶,折线图与环形图 st.columns(2) 并排——看板完工39。
8. 作者的判断与证据¶
判断:CSV 当数据源是教学妥协,不是实践推荐。 书里明说真实看板不会用静态 CSV, 本章是为了聚焦「数据进应用之后的事」,接数仓留到第 07 章40。这种「先简化、后补回」 的节奏贯穿全书,作者每次都说破,值得肯定。
判断:配置驱动(Metric 数据类 + metrics 字典)优于硬编码。 依据回溯到第 03 章的 unit_config 原则41。证据是这个设计在章内三次复用(指标条、折线、环形),第四次扩展 (加指标)只动配置——结构性证据,充分。
判断:饼图「虽有争议但够用」。 作者给了替代方案(条形图)和理由42。 我们认可其诚实,但补充:给 CEO 的占比场景,水平条形图几乎总是更好的默认选择; 保留饼图是因为它「视觉上显然在展示部分与整体」。
9. 边界与局限¶
- 90 MB CSV + 单机内存:这个量级 pandas 还扛得住,再大就要换 Arrow 后端、 分块读取或直接上数据库/数仓——书里只点了 Arrow,没展开。
- 缓存的失效语义只有 TTL:数据每天变、一天内多次变,1 天的 TTL 就意味着最长 滞后一天;更细的数据新鲜度需求需要手动清缓存或更复杂的机制,本章没讲。
- st.cache_data 共享缓存的暗面:多用户应用里,缓存是全应用共享的; 本章数据「所有用户看同一份」所以安全,但用户相关的数据绝不能这么缓存—— 书里预告了「later chapters 会遇到意外的结果」,第 08 章的 CRUD 应用会正面撞上。
- 性能预算没有量化:100 万行数据,每次交互都跑「过滤 + 分组聚合」, 延迟多少毫秒、什么量级会卡,书里没给数;真实项目要自己压测。
include_groups=False是 pandas 新版本的兼容性细节,旧代码示例里常常没有, 照抄老教程会报警告——版本敏感点。
判断(我们的,不是书里的): 这一章的真正贡献不是「教会 Plotly」, 而是示范了数据应用的标准解剖:一条「读 → 缓存 → 过滤 → 呈现」的流水线, 加上「指标即配置」的复用结构。这两样东西在任何数据框架(不止 Streamlit)里都成立, 是数据工程可迁移的骨架。对编程语言与系统研究,这一章还提供了一个好案例: 声明式缓存(贴一行装饰器)如何用最小的语法成本解决执行模型的最大痛点—— 值得和 React 的 useMemo、数据库的物化视图对照着看。 如果错,会错在: 「配置驱动」的收益随团队规模变化——单人快脚本里, Metric 数据类 + 三个模块是过度设计(第 04 章自己刚示范过单文件风格); 本章没说清什么规模该切到这套结构,分界只能靠读者自己掂量。
10. 可带走的¶
- 数据类应用的流水线:读 → 缓存 → 全局过滤 → 呈现;过滤先行,因为所有部件都消费过滤结果。
pd.read_csv()一行读表,df.info()体检(行数、列、类型、内存),df.head()看样例。- dataframe 选列
df[['a','b']]、布尔索引df[df['col'].isin(values)]、聚合groupby(键).apply(函数)——pandas 三大句式。 @st.cache_data(ttl=..., show_spinner=...):慢函数贴一行,全应用共享缓存;TTL 决定数据新鲜度。- 改了被缓存的函数要重启服务器,刷新页面没用。
- 选项从数据里取(unique()),不从代码里写死。
- st.multiselect 返回选中列表;「没选 = 不过滤」要显式实现。
- 比率指标 = 先求和再相除,不能平均行级比率;占比图只适用于可加总的量。
- st.set_page_config(layout='wide') 必须是第一个 Streamlit 命令。
- Plotly 三步:Figure() → add_trace(Scatter/Pie) → st.plotly_chart;st.plotly_chart 白送缩放/悬停/下载。
- st.tabs 切换不重跑、radio 会;用加载时间换切换顺滑。
- humanize.metric 把 25125367 压成 25.1M——给决策者的数字先压认知负担。
11. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 场景与四个关键指标 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:45搜「paper report」) |
| 数据文件与主键 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:70搜「Comma-Separated Values」) |
| 范围外(不下钻、不预测) | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:117搜「out of scope」) |
| 设计与数据流图(figure 6.3) | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:205搜「Flow of logic and data」) |
| dataframe 定义与 read_csv | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:226搜「pandas library」) |
| df.info() 输出与 dtypes | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:274搜「1035000 entries」) |
| Path(file) 路径拼接 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:358搜「relative to the」) |
| st.write 瑞士军刀 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:406搜「Swiss Army」) |
| st.cache_data 定义与装饰器 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:449搜「caching」) |
| ttl 与缓存流程图 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:491搜「expiry date」) |
| session_state 缓存的两个不足 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:440搜「solve a couple」) |
| 缓存共享给所有用户 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:537搜「available to all users」) |
| 改缓存函数要重启 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:543搜「hard reset」) |
| unique() 动态取选项 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:590搜「dynamically, from the data itself」) |
| st.multiselect 与 filter 面板 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:610搜「st.multiselect」) |
| st.expander | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:652搜「collapsible box」) |
| set_page_config(layout='wide') | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:677搜「centered layout」) |
| apply_filters 与布尔索引 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:796搜「boolean indexing」) |
| st.date_input 与日期范围 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:833搜「date range selector」) |
| 四个指标定义与手算例 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:978搜「Total sales—Sum」) |
| Metric dataclass 与 metric_config | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:999搜「reasonably general」) |
| humanize 格式化 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1117搜「humanize」) |
| metric_bar 与居中 hack、st.container | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1148搜「metric bar」) |
| groupby 聚合出时间序列 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1257搜「Obtaining a time series」) |
| Plotly Scatter 折线图 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1357搜「Plotly Graph Objects」) |
| st.tabs 与「切换不重跑」 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1482搜「does not trigger」) |
| 饼图只用两个指标的理由 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1514搜「add up to 100%」) |
| go.Pie 环形图 | 6 A dashboard fit for a CEO | text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1573搜「A Plotly pie chart」) |
-
出处:「6 A dashboard fit for a CEO」第 40–55 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:45,搜「paper report」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 57–61 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:60,搜「track key metrics」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 99–102 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:100,搜「margin percentage」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 116–125 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:117,搜「What's out of scope」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 86–96 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:95,搜「134.91」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 97–98 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:97,搜「primary key」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 226–245 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:222,搜「quintessential Python library」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 309–320 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:319,搜「head()」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 319–329 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:320,搜「first five rows」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 295–307 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:301,搜「dtype_backend」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 337–388 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:359,搜「you’re executing a script from」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 389–421 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:406,搜「Swiss Army」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 423–430 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:423,搜「takes a few seconds」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 432–446 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:437,搜「one way to deal with the problem」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 447–477 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:449,搜「st.cache_data is Streamlit's way」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 483–540 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:491,搜「expiry date」)与第 536–540 段(共享缓存)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 542–547 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:543,搜「hard reset」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 610–655 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:610,搜「st.multiselect」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 584–609 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:590,搜「dynamically, from the data itself」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 778–808 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:796,搜「boolean indexing」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 833–873 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:833,搜「date range selector」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 886–917 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:917,搜「element-wise logical AND」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 675–686 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:677,搜「centered layout」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 687–742 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:687,搜「user-friendly labels」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 963–983 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:978,搜「Total sales—Sum」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 984–995 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:995,搜「43.48」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 1026–1100 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1096,搜「division-by-zero」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 1112–1145 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1127,搜「humanize.metric」)。25,125,367 → 25.1M 的例子见第 1136–1138 段。 ↩ -
出处:「6 A dashboard fit for a CEO」第 1147–1225 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1201,搜「layout hack」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 1207–1218 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1216,搜「No data to display」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 1286–1322 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1289,搜「include_groups」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 1263–1285 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1283,搜「2800」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 1350–1417 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1357,搜「Plotly Graph Objects」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 1473–1477 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1475,搜「zoom in to」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 1479–1485 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1482,搜「does not trigger an app」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 1527–1572 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1527,搜「Getting data into the right shape」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 1502–1526 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1514,搜「add up to 100%」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 1495–1500 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1495,搜「controversial choice」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 1619–1645 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1626,搜「side-by-side」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 72–80 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:75,搜「not particularly efficient」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 997–1008 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:999,搜「reasonably general」)。 ↩ -
出处:「6 A dashboard fit for a CEO」第 1495–1500 段(
text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1496,搜「bar charts are」)。 ↩