编程语言研究书库

CEO 看板 — pandas、缓存,和一条「原始数据 → 指标」的流水线

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. 可带走的

  1. 数据类应用的流水线:读 → 缓存 → 全局过滤 → 呈现;过滤先行,因为所有部件都消费过滤结果。
  2. pd.read_csv() 一行读表,df.info() 体检(行数、列、类型、内存),df.head() 看样例。
  3. dataframe 选列 df[['a','b']]、布尔索引 df[df['col'].isin(values)]、聚合 groupby(键).apply(函数)——pandas 三大句式。
  4. @st.cache_data(ttl=..., show_spinner=...):慢函数贴一行,全应用共享缓存;TTL 决定数据新鲜度。
  5. 改了被缓存的函数要重启服务器,刷新页面没用。
  6. 选项从数据里取(unique()),不从代码里写死。
  7. st.multiselect 返回选中列表;「没选 = 不过滤」要显式实现。
  8. 比率指标 = 先求和再相除,不能平均行级比率;占比图只适用于可加总的量。
  9. st.set_page_config(layout='wide') 必须是第一个 Streamlit 命令。
  10. Plotly 三步:Figure() → add_trace(Scatter/Pie) → st.plotly_chart;st.plotly_chart 白送缩放/悬停/下载。
  11. st.tabs 切换不重跑、radio 会;用加载时间换切换顺滑。
  12. humanize.metric 把 25125367 压成 25.1M——给决策者的数字先压认知负担。

11. 原文地图

主题原书章原文位置
场景与四个关键指标6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:45搜「paper report」)
数据文件与主键6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:70搜「Comma-Separated Values」)
范围外(不下钻、不预测)6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:117搜「out of scope」)
设计与数据流图(figure 6.3)6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:205搜「Flow of logic and data」)
dataframe 定义与 read_csv6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:226搜「pandas library」)
df.info() 输出与 dtypes6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:274搜「1035000 entries」)
Path(file) 路径拼接6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:358搜「relative to the」)
st.write 瑞士军刀6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:406搜「Swiss Army」)
st.cache_data 定义与装饰器6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:449搜「caching」)
ttl 与缓存流程图6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:491搜「expiry date」)
session_state 缓存的两个不足6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:440搜「solve a couple」)
缓存共享给所有用户6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:537搜「available to all users」)
改缓存函数要重启6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:543搜「hard reset」)
unique() 动态取选项6 A dashboard fit for a CEOtext/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 CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:610搜「st.multiselect」)
st.expander6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:652搜「collapsible box」)
set_page_config(layout='wide')6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:677搜「centered layout」)
apply_filters 与布尔索引6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:796搜「boolean indexing」)
st.date_input 与日期范围6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:833搜「date range selector」)
四个指标定义与手算例6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:978搜「Total sales—Sum」)
Metric dataclass 与 metric_config6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:999搜「reasonably general」)
humanize 格式化6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1117搜「humanize」)
metric_bar 与居中 hack、st.container6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1148搜「metric bar」)
groupby 聚合出时间序列6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1257搜「Obtaining a time series」)
Plotly Scatter 折线图6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1357搜「Plotly Graph Objects」)
st.tabs 与「切换不重跑」6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1482搜「does not trigger」)
饼图只用两个指标的理由6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1514搜「add up to 100%」)
go.Pie 环形图6 A dashboard fit for a CEOtext/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1573搜「A Plotly pie chart」)

Footnotes

  1. 出处:「6 A dashboard fit for a CEO」第 40–55 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:45,搜「paper report」)。 ↩

  2. 出处:「6 A dashboard fit for a CEO」第 57–61 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:60,搜「track key metrics」)。 ↩

  3. 出处:「6 A dashboard fit for a CEO」第 99–102 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:100,搜「margin percentage」)。 ↩

  4. 出处:「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」)。 ↩

  5. 出处:「6 A dashboard fit for a CEO」第 86–96 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:95,搜「134.91」)。 ↩

  6. 出处:「6 A dashboard fit for a CEO」第 97–98 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:97,搜「primary key」)。 ↩

  7. 出处:「6 A dashboard fit for a CEO」第 226–245 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:222,搜「quintessential Python library」)。 ↩

  8. 出处:「6 A dashboard fit for a CEO」第 309–320 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:319,搜「head()」)。 ↩

  9. 出处:「6 A dashboard fit for a CEO」第 319–329 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:320,搜「first five rows」)。 ↩

  10. 出处:「6 A dashboard fit for a CEO」第 295–307 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:301,搜「dtype_backend」)。 ↩

  11. 出处:「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」)。 ↩

  12. 出处:「6 A dashboard fit for a CEO」第 389–421 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:406,搜「Swiss Army」)。 ↩

  13. 出处:「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」)。 ↩

  14. 出处:「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」)。 ↩

  15. 出处:「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」)。 ↩

  16. 出处:「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 段(共享缓存)。 ↩

  17. 出处:「6 A dashboard fit for a CEO」第 542–547 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:543,搜「hard reset」)。 ↩

  18. 出处:「6 A dashboard fit for a CEO」第 610–655 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:610,搜「st.multiselect」)。 ↩

  19. 出处:「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」)。 ↩

  20. 出处:「6 A dashboard fit for a CEO」第 778–808 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:796,搜「boolean indexing」)。 ↩

  21. 出处:「6 A dashboard fit for a CEO」第 833–873 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:833,搜「date range selector」)。 ↩

  22. 出处:「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」)。 ↩

  23. 出处:「6 A dashboard fit for a CEO」第 675–686 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:677,搜「centered layout」)。 ↩

  24. 出处:「6 A dashboard fit for a CEO」第 687–742 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:687,搜「user-friendly labels」)。 ↩

  25. 出处:「6 A dashboard fit for a CEO」第 963–983 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:978,搜「Total sales—Sum」)。 ↩

  26. 出处:「6 A dashboard fit for a CEO」第 984–995 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:995,搜「43.48」)。 ↩

  27. 出处:「6 A dashboard fit for a CEO」第 1026–1100 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1096,搜「division-by-zero」)。 ↩

  28. 出处:「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 段。 ↩

  29. 出处:「6 A dashboard fit for a CEO」第 1147–1225 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1201,搜「layout hack」)。 ↩

  30. 出处:「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」)。 ↩

  31. 出处:「6 A dashboard fit for a CEO」第 1286–1322 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1289,搜「include_groups」)。 ↩

  32. 出处:「6 A dashboard fit for a CEO」第 1263–1285 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1283,搜「2800」)。 ↩

  33. 出处:「6 A dashboard fit for a CEO」第 1350–1417 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1357,搜「Plotly Graph Objects」)。 ↩

  34. 出处:「6 A dashboard fit for a CEO」第 1473–1477 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1475,搜「zoom in to」)。 ↩

  35. 出处:「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」)。 ↩

  36. 出处:「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」)。 ↩

  37. 出处:「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%」)。 ↩

  38. 出处:「6 A dashboard fit for a CEO」第 1495–1500 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1495,搜「controversial choice」)。 ↩

  39. 出处:「6 A dashboard fit for a CEO」第 1619–1645 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1626,搜「side-by-side」)。 ↩

  40. 出处:「6 A dashboard fit for a CEO」第 72–80 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:75,搜「not particularly efficient」)。 ↩

  41. 出处:「6 A dashboard fit for a CEO」第 997–1008 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:999,搜「reasonably general」)。 ↩

  42. 出处:「6 A dashboard fit for a CEO」第 1495–1500 段(text/15-ch06-6-a-dashboard-fit-for-a-ceo.txt:1496,搜「bar charts are」)。 ↩

On this page