← 返回文章列表

用 DuckDB 复算财报:从 12 行原始数据到收入结构表

作者:杨轩

公司分析里的百分比,怎样从财报原表一步步算回来?

用Shopify与Duolingo两期公开财报,演示保留原值、统一单位、核对期间与收入对账,附12行数据、SQL和可运行脚本。

读完一篇公司分析,怎样确认里面的百分比能算回来?可以先做一张很小的表:保留原值、单位、期间和出处,再把计算写进脚本。

这次用Shopify、Duolingo各两年的收入数字,复算“订阅收入占总收入的比例”。下载包提供12行公开财报事实、SQL、Python脚本和预期结果。它是一份固定样本练习,使用2025年报披露的FY2024/FY2025数据,不是自动更新的财务数据库。

已有的Shopify案例和Duolingo案例解释商业模式;这篇补上从原表到计算结果的过程。想先弄清脚本依赖怎么准备,可以读uv交付教程。

先把四个结果跑出来

安装uv,下载财报复算包,解压进入目录,运行:

uv run --locked --script financial.py --out result-01.json
uv run --no-project --python 3.12 check_result.py result-01.json

第一条读取包内CSV、执行SQL并输出JSON;第二条核对固定样本的输入/SQL哈希、四个结果和收入对账,成功时显示OK。第一次准备Python和依赖可能需要联网,财报计算本身不调用接口,也不需要账号或API key。

输出目录须已存在,已有文件不覆盖;再次运行换成result-02.json。包内的核对脚本只适用于这12行数据,换数据后需要自己编写预期结果。

公司与财年 订阅收入,百万美元 总收入,百万美元 订阅占比
Shopify FY2024 2,350 8,880 26.46%
Shopify FY2025 2,752 11,556 23.81%
Duolingo FY2024 607.531 748.024 81.22%
Duolingo FY2025 873.442 1,037.589 84.18%

这张表按公司分组展示,不是排名。两家的订阅科目对应不同产品与业务;占比高低不能直接判断模式更好、利润更高或用户更忠诚。

第一步:原表数字与说明一起录入

Shopify来自2025年10-K的Results of Operations收入表,原单位为百万美元;Duolingo来自2025年10-K的收入分类表,原单位为千美元。两份报告同时列出2024比较数,因此这里的2024数据也从2025报告摘录。

例如,Duolingo FY2025的订阅收入原值是873442,单位是USD_thousand。不能直接把它与Shopify原表的2752放进同一金额列。

打开raw_facts.csv,每行除了数值,还保留这些字段:

字段 为什么保留
company、fiscal_year 确认哪家公司、哪一财年
period_start、period_end 防止只凭“2025”混入一个季度或不同期间
source_label 保留原表科目名,方便返回原文核对
value、unit 原值与单位分开,避免看不出换算过程
source_url、source_table 找回原表,而不是只剩一个百分比
role 本例为计算手工指定的分项、其他、总收入角色

这里的role不是自动识别结果。Shopify的other映射Merchant solutions,Duolingo的other映射Other。后者不全是广告,前者也不是“其他杂项”;原表科目名必须一起保留。

第二步:把单位换算留在SQL里

revenue_mix.sql把金额统一成百万美元,核心规则是:

CAST(value AS DECIMAL(18,0)) *
CAST(CASE unit
    WHEN 'USD_million' THEN '1'
    WHEN 'USD_thousand' THEN '0.001'
    WHEN 'USD' THEN '0.000001'
END AS DECIMAL(7,6))

因此,873442千美元变成873.442百万美元。这是数值单位转换,不是汇率换算;脚本遇到未支持的单位会报错。

原值保留在CSV,转换写在SQL,读者就能区分“原表本来是多少”和“为了展示换成多少”。接着按公司、财年及起止日期分组,把三种收入角色放到同一行。

本例原值限定为非负整数,SQL用DECIMAL乘法做换算和汇总。DuckDB的DECIMAL除法会返回近似浮点,因此最终比例由Python Decimal计算,按ROUND_HALF_UP保留两位小数。类型行为见DuckDB数值文档。这只是本小样本的实现选择,不是完整财务计算系统。

第三步:先对账,再计算百分比

每个公司年度必须有三条记录,并且期间一致:

订阅分项 + 其余收入分项 = 总收入

例如,Duolingo FY2025在原单位下为:

873442 + 164147 = 1037589

统一为百万美元后,再算:

873.442 ÷ 1037.589 × 100% ≈ 84.18%

程序也会拒绝重复的公司/财年/收入角色、缺少分项、总收入为零或期间不一致。它不会因为算出了一个像样的百分比,就跳过收入对账。

这12行数据在原表取整后恰好能精确对上。换到别的报告,可能存在舍入差、更多分项或不同列示方法;先解释差异,再明确调整规则,不能自动补一笔“其他收入”凑平。

哪些判断仍要人来做

同一财年名称,不一定是同一期间。 本包两家公司都截至12月31日;换到其他公司,要保留真实起止日期。程序只能检查填入的三条日期是否一致,不能证明你录入的就是全年数据。

同一个“订阅”标签,不一定是同一种业务。 脚本知道手工指定的角色,不知道科目定义是否合理。读表与映射仍要返回原文确认,不能仅靠英文名称自动合并。

对账通过,不等于录入正确。 如果分项和总数一起抄错,仍可能对得上。来源定位、人工核对与算术检查是相互补充的;程序不自动判断数据是否最新或忠实原表。

收入占比,不是转化率、续费率或利润率。 Shopify两年的订阅收入本身增长,占比却下降,说明“占比下降”也不能直接改写成“订阅收入减少”。更多经营解释回到两篇案例阅读。

拿走这份方法,怎样换成自己的研究

先只选一家公司的一个完整期间,找出收入分项、总数、原单位和出处。人工确认分项能对账,再做角色映射和复算。不要一开始就抓几百家公司,最后才发现单位或口径混在一起。

下载包包含CSV、SQL、脚本锁、预期结果、核对命令与文件哈希;不包含完整年报、实时抓取服务或第三方运行环境。需要检查行情数据重复、缺口与复权口径时,另看AKShare+DuckDB数据检查,它解决的是另一类数据任务。

本次在macOS arm64、uv0.11.21、Python3.12.2、DuckDB1.5.6下复算,并检查单位等价换算、期间错配、重复/缺项、收入不平及已有输出保护。使用已有解释器和依赖缓存,未在全新系统、Linux或Windows实测。来源核对日为2026年11月1日。

本文为公开资料研究与AI辅助教程;财务事实来自标明的固定财年报告,代码和复算流程由本站整理,不作为最新业绩、本人投资经历或收益记录。

本文代码与样本:资料与下载 →

让分享,成为交流的开始。

关于文章、AI 实践或阅读,有想法欢迎来信。