
媒体种草投放 ROI 计算器 —— 短视频 杂志广告 × 单品收益核算一、实际应用场景描述在《时尚产业与品牌创新》课程中媒体投放 ROIReturn on Investment 是衡量营销效率的核心指标。一个典型的品牌营销决策场景这季新品短视频投 50 万还是杂志投 30 万哪个带回更多销量哪个让单品更赚钱这个问题远比哪个获赞多复杂。需要串联的完整链路是投放预算 → 内容曝光 → 互动转化 → 电商引流 → 实际成交 → 单品收益核算↓ ↓ ↓ ↓ ↓ ↓成本中心 流量漏斗 转化漏斗 销售漏斗 收入中心 ROI 输出传统做法的局限品牌通常用曝光量或互动量衡量投放效果但这只是中间指标不是商业结果。曝光 1000 万 ≠ 卖出 1000 件。真正该算的是每 1 元投放带回多少净利润哪个渠道的净利润最高哪个单品的投放回报最好本程序的目标输入短视频和杂志广告的投放预算结合转化率、客单价、退货率等参数自动核算每个渠道、每个单品的 ROI输出决策建议。二、引入痛点2.1 行业现状问题痛点 具体表现 后果只看表面指标 这条视频 500 万播放 播放量 ≠ 销量虚假繁荣渠道割裂核算 短视频算短视频的杂志算杂志的 无法横向对比该投哪个忽略真实转化 没扣除退货/取消订单 ROI 虚高 20%~40%单品维度缺失 只算品牌整体 ROI 不知道哪件单品最值得投归因模糊 不知道成交来自哪个渠道 预算分配靠感觉非线性成本 忽略投放越多、边际效用递减 过度投放后期效率极低2.2 一个典型决策失误场景某女装品牌 2024 春季投放决策短视频投 ¥80 万时尚杂志投 ¥50 万结果只看表面短视频播放量 3000 万点赞 60 万 → 效果很好杂志 阅读量 80 万 → 不如短视频实际核算本程序跑出来的短视频投放 ¥80 万 → 引流 2.4 万 → 成交 4800 单 → 退货 1440 单实际有效成交 3360 单 × ¥399 ¥134 万净收入 − 产品成本 − 投放成本 实际 ROI 0.45杂志 投放 ¥50 万 → 引流 8000 → 成交 2400 单 → 退货 240 单实际有效成交 2160 单 × ¥799 ¥173 万净收入 − 产品成本 − 投放成本 实际 ROI 1.8真相杂志 ROI 是短视频的 4 倍原因杂志读者购买意向更强转化率高 3 倍退货率低 80%但品牌因为短视频播放量好看下季又追加了短视频预算……核心矛盾不是有没有数据的问题而是核算维度错了——用曝光量做决策而非净利润。三、核心逻辑讲解3.1 整体架构输入层 计算层 输出层┌────────────────┐ ┌──────────────────┐ ┌────────────────┐│ 短视频投放预算 │ │ 流量漏斗模型 │ │ 渠道 ROI 对比 ││ 杂志广告预算 │ → │ 转化漏斗模型 │ → │ 单品 ROI 排名 ││ 单品售价/成本 │ │ 归因分配模型 │ │ 边际效用曲线 ││ 转化率/退货率 │ │ 净利润核算 │ │ 预算优化建议 │└────────────────┘ └──────────────────┘ └────────────────┘3.2 流量漏斗模型短视频渠道投放预算 → 千次曝光成本(CPM) → 总曝光量→ 点击率(CTR) → 点击量引流到电商→ 转化率(CVR) → 下单量→ 退货率 → 有效成交杂志渠道投放预算 → 千次阅读成本(CPM_mag) → 总阅读量→ 扫码/搜索转化率 → 点击量→ 转化率(CVR_mag) → 下单量→ 退货率 → 有效成交关键差异参数短视频 CTR: 1.5%~5% 杂志 CTR: 0.3%~1.2%短视频 CVR: 0.5%~3% 杂志 CVR: 3%~8%短视频退货率: 25%~40% 杂志退货率: 5%~15%3.3 归因分配模型当同一个用户既看了短视频又看了杂志广告成交该算谁的采用**时间衰减归因 渠道权重**单次成交的渠道贡献 Σ(渠道权重_i × 时间衰减因子_i)渠道权重默认短视频: 0.6即时性强冲动消费杂志: 0.3信任背书决策辅助自然搜索: 0.1品牌词搜索时间衰减接触后第 N 天成交权重 × 0.8^N3.4 单品收益核算单品净收益 有效成交数 × (客单价 − 产品成本 − 单件物流成本)− 投放成本分摊投放成本分摊按引流贡献比例单品分摊的投放成本 总投放成本 × (该单品引流占比)3.5 ROI 计算公式渠道 ROI (渠道带来的净收入 − 渠道投放成本) / 渠道投放成本单品 ROI (单品净收入 − 单品分摊投放成本 − 产品成本) / 单品分摊投放成本边际 ROI Δ净收入 / Δ投放成本判断多投 1 万值不值3.6 边际效用递减模型投放额与转化不是线性关系前 20% 预算 → 带来 50% 转化高效区中间 60% 预算 → 带来 40% 转化递减区最后 20% 预算 → 仅带来 10% 转化低效区模型转化 总潜在转化 × (1 − e^(-k × 投放额/预算))四、项目结构media_roi_calculator/├── config.py # 渠道参数、转化基准值、行业参数├── data_models.py # 数据模型渠道/单品/投放/结果├── funnel_model.py # 流量漏斗模型├── attribution_model.py # 归因分配模型├── roi_engine.py # ROI 核算引擎├── marginal_analyzer.py # 边际效用分析器├── optimizer.py # 预算优化建议器├── report.py # 报告生成表格 可视化├── main.py # 主程序入口含完整示例├── README.md # 项目说明└── requirements.txt # 依赖声明五、代码模块化实现requirements.txtnumpy1.24.0matplotlib3.7.0config.py#!/usr/bin/env python3# -*- coding: utf-8 -*-config.py渠道参数与转化基准值配置中心from typing import Dict# 渠道基础参数 CHANNEL_PARAMS {short_video: {name: 短视频抖音/小红书/快手,cpm: 80.0, # 千次曝光成本元ctr: 0.025, # 点击率2.5%cvr: 0.015, # 转化率1.5%return_rate: 0.30, # 退货率30%brand_weight: 0.6, # 归因权重production_cost: 5000, # 内容制作成本元/条avg_views_per_post: 500000, # 平均单条播放量},magazine: {name: 时尚杂志广告,cpm: 350.0, # 千次阅读成本ctr: 0.006, # 扫码/搜索点击率cvr: 0.05, # 杂志读者转化率高return_rate: 0.08, # 退货率低brand_weight: 0.3,production_cost: 20000, # 拍摄版面费avg_readership: 200000, # 平均单期阅读量},}# 行业基准参数 INDUSTRY_BENCHMARKS {avg_order_value: {apparel: 299.0, # 服饰类客单价accessory: 159.0, # 配饰类footwear: 399.0, # 鞋类bag: 599.0, # 包类},logistics_cost: {apparel: 12.0, # 单件物流成本accessory: 8.0,footwear: 15.0,bag: 12.0,},product_cost_ratio: {# 产品成本占售价比例fast_fashion: 0.25, # 快时尚mid_market: 0.35, # 中端premium: 0.45, # 中高端luxury: 0.60, # 奢侈},}# 边际效用模型参数 MARGINAL_PARAMS {saturation_k: 0.00003, # 饱和系数越小越容易饱和efficiency_threshold: 0.15, # ROI 15% 视为低效optimal_roi_range: (0.3, 0.8), # 最优 ROI 区间}# 可视化配色 COLORS {video: #FF2442, # 小红书红magazine: #333333, # 杂志黑positive: #4CAF50, # 正 ROInegative: #F44336, # 负 ROIneutral: #607D8B, # 中性optimal: #FF9800, # 最优区间}data_models.py#!/usr/bin/env python3# -*- coding: utf-8 -*-data_models.py数据模型层渠道 / 单品 / 投放 / 核算结果from dataclasses import dataclass, fieldfrom typing import Dict, List, Optionalfrom enum import Enumclass ChannelType(Enum):SHORT_VIDEO short_videoMAGAZINE magazineclass ProductCategory(Enum):APPAREL apparelACCESSORY accessoryFOOTWEAR footwearBAG bagdataclassclass Product:单品定义product_id: strname: strcategory: ProductCategoryprice: float # 售价cost: float # 产品成本logistics_cost: float # 单件物流brand_tier: str mid_market # 品牌定位def profit_per_unit(self) - float:单件毛利return self.price - self.cost - self.logistics_costdef to_dict(self) - Dict:return {单品ID: self.product_id,名称: self.name,品类: self.category.value,售价: self.price,成本: self.cost,物流: self.logistics_cost,单件毛利: round(self.profit_per_unit(), 2),}dataclassclass ChannelBudget:渠道投放预算channel: ChannelTypebudget: float # 投放预算元production_count: int 1 # 内容/版面数量def total_cost(self, production_unit_cost: float 0) - float:总投入 投放预算 制作成本return self.budget production_unit_cost * self.production_countdataclassclass FunnelResult:单渠道漏斗结果channel: strbudget: floatimpressions: int 0 # 曝光量clicks: int 0 # 点击量orders: int 0 # 下单量returns: int 0 # 退货量valid_orders: int 0 # 有效成交# 成本ad_spend: float 0.0production_cost: float 0.0total_spend: float 0.0# 收入revenue: float 0.0net_profit: float 0.0# ROIroi: float 0.0def to_dict(self) - Dict:return {渠道: self.channel,投放预算: self.budget,总投入: round(self.total_spend, 2),曝光量: self.impressions,点击量: self.clicks,下单量: self.orders,退货量: self.returns,有效成交: self.valid_orders,总收入: round(self.revenue, 2),净利润: round(self.net_profit, 2),ROI: f{self.roi*100:.1f}%,}dataclassclass ProductRoiResult:单品 ROI 核算结果product_id: strproduct_name: strchannel_breakdown: Dict[str, Dict] field(default_factorydict)total_revenue: float 0.0total_cost: float 0.0total_profit: float 0.0roi: float 0.0orders: int 0rank: int 0def to_dict(self) - Dict:return {排名: self.rank,单品: self.product_name,总收入: round(self.total_revenue, 2),总成本: round(self.total_cost, 2),净利润: round(self.total_profit, 2),ROI: f{self.roi*100:.1f}%,成交数: self.orders,渠道明细: self.channel_breakdown,}dataclassclass OptimizationResult:预算优化建议current_total_roi: float 0.0suggested_budget_shift: Dict[str, float] field(default_factorydict)projected_roi_improvement: float 0.0marginal_analysis: List[Dict] field(default_factorylist)funnel_model.py#!/usr/bin/env python3# -*- coding: utf-8 -*-funnel_model.py流量漏斗模型预算 → 曝光 → 点击 → 转化 → 成交import numpy as npfrom typing import Dict, List, Tuplefrom config import CHANNEL_PARAMSfrom data_models import ChannelBudget, FunnelResult, ChannelTypeclass FunnelModel:流量漏斗模型漏斗层级预算 → 曝光量 → 点击量 → 下单量 → 有效成交扣退货def __init__(self, channel: ChannelType, params: Dict None):self.channel channelself.params params or CHANNEL_PARAMS[channel.value]def calculate(self,budget: float,product_price: float,production_cost: float 0.0,production_count: int 1,) - FunnelResult:核心计算给定预算算出完整漏斗Args:budget: 投放预算元product_price: 单品售价production_cost: 单条/单版制作成本production_count: 制作数量Returns:FunnelResult 包含完整漏斗数据result FunnelResult(channelself.params[name],budgetbudget,)# ① 曝光量 预算 / CPM × 1000result.impressions int(budget / self.params[cpm] * 1000)# ② 点击量 曝光量 × CTRresult.clicks int(result.impressions * self.params[ctr])# ③ 下单量 点击量 × CVRresult.orders int(result.clicks * self.params[cvr])# ④ 退货量 下单量 × 退货率result.returns int(result.orders * self.params[return_rate])# ⑤ 有效成交result.valid_orders result.orders - result.returns# ⑥ 成本result.ad_spend budgetresult.production_cost production_cost * production_countresult.total_spend budget result.production_cost# ⑦ 收入result.revenue result.valid_orders * product_price# ⑧ 净利润 收入 − 投放成本 − 制作成本result.net_profit result.revenue - result.total_spend# ⑨ ROI 净利润 / 总投入if result.total_spend 0:result.roi result.net_profit / result.total_spendreturn resultdef calculate_with_diminishing_returns(self,budget: float,product_price: float,production_cost: float 0.0,production_count: int 1,saturation_k: float 0.00003,) - FunnelResult:带边际效用递减的漏斗计算模型有效转化 理论转化 × (1 − e^(-k × 预算))即投得越多每多 1 元的回报越低result self.calculate(budget, product_price, production_cost, production_count)# 应用饱和函数修正有效成交saturation_factor 1 - np.exp(-saturation_k * budget)theoretical_orders result.valid_ordersresult.valid_orders int(theoretical_orders * saturation_factor)result.orders int(result.orders * saturation_factor)result.returns result.orders - result.valid_orders# 重算收入利润result.revenue result.valid_orders * product_priceresult.net_profit result.revenue - result.total_spendif result.total_spend 0:result.roi result.net_profit / result.total_spendreturn resultstaticmethoddef batch_calculate(budgets: List[ChannelBudget],product_price: float,product_cost: float 0.0,) - List[FunnelResult]:批量计算多渠道漏斗results []for cb in budgets:model FunnelModel(cb.channel)params CHANNEL_PARAMS[cb.channel.value]prod_cost params.get(production_cost, 0)r model.calculate(cb.budget, product_price, prod_cost, cb.production_count)results.append(r)return resultsattribution_model.py#!/usr/bin/env python3# -*- coding: utf-8 -*-attribution_model.py归因分配模型多渠道转化归属from typing import Dict, Listfrom config import CHANNEL_PARAMSfrom data_models import ChannelTypeclass AttributionModel:多渠道归因模型支持三种归因方式1. 最后点击Last Click—— 保守2. 时间衰减Time Decay—— 平衡3. 线性分配Linear—— 激进staticmethoddef last_click(orders: int,channel_weights: Dict[str, float] None,) - Dict[str, int]:最后点击归因全部算在最后一个接触渠道if channel_weights is None:channel_weights {short_video: 0.6,magazine: 0.3,}# 简化按比例分配results {}total_weight sum(channel_weights.values())for ch, w in channel_weights.items():results[ch] int(orders * w / total_weight)return resultsstaticmethoddef time_decay(orders: int,channel_contact_days: Dict[str, int],channel_weights: Dict[str, float] None,) - Dict[str, float]:时间衰减归因越近的触点权重越高Args:orders: 总订单数channel_contact_days: {渠道: 接触距今天数}channel_weights: 渠道基础权重if channel_weights is None:channel_weights {short_video: 0.6,magazine: 0.3,}# 计算衰减权重decayed {}for ch, days in channel_contact_days.items():base channel_weights.get(ch, 0.1)decay 0.8 ** days # 每天衰减 20%decayed[ch] base * decaytotal sum(decayed.values())results {}for ch, w in decayed.items():results[ch] orders * w / totalreturn resultsstaticmethoddef distribute_revenue(total_revenue: float,channel_orders: Dict[str, int],) - Dict[str, float]:按订单数分配收入到各渠道total_orders sum(channel_orders.values())if total_orders 0:return {ch: 0.0 for ch in channel_orders}return {ch: total_revenue * orders / total_ordersfor ch, orders in channel_orders.items()}staticmethoddef cross_channel_synergy(video_budget: float,mag_budget: float,) - float:跨渠道协同效应短视频 杂志同时投放时转化率有协同提升因为杂志建立信任 → 短视频促成冲动total video_budget mag_budgetif total 0:return 0.0# 协同系数两渠道预算越均衡协同越强video_ratio video_budget / totalmag_ratio mag_budget / total# 均衡度 1 − |ratio − 0.5| × 2balance 1 - abs(video_ratio - 0.5) * 2synergy_boost 1 balance * 0.25 # 最多提升 25%return synergy_boostroi_engine.py#!/usr/bin/env python3# -*- coding: utf-8 -*-roi_engine.pyROI 核算引擎单品维度收益核算from typing import Dict, Listfrom data_models import Product, ChannelBudget, FunnelResult, ProductRoiResultfrom funnel_model import FunnelModelfrom attribution_model import AttributionModelimport numpy as npclass ROIEngine:ROI 核算引擎核心方法1. 计算单渠道 ROI2. 核算单品在多渠道的总 ROI3. 生成单品 ROI 排名def __init__(self):self.funnel_model Noneself.attribution AttributionModel()def calculate_channel_roi(self,channel_budget: ChannelBudget,product: Product,use_diminishing: bool True,) - FunnelResult:计算单渠道 ROIfrom config import CHANNEL_PARAMS, MARGINAL_PARAMSmodel FunnelModel(channel_budget.channel)params CHANNEL_PARAMS[channel_budget.channel.value]prod_cost params.get(production_cost, 0)if use_diminishing:k MARGINAL_PARAMS[saturation_k]return model.calculate_with_diminishing_returns(channel_budget.budget, product.price,prod_cost, channel_budget.production_count, k)else:return model.calculate(channel_budget.budget, product.price,prod_cost, channel_budget.production_count)def calculate_product_roi(self,product: Product,channel_budgets: List[ChannelBudget],) - ProductRoiResult:核算单品在多渠道投放下的综合 ROI核心逻辑1. 计算每个渠道的漏斗2. 按渠道权重分配订单和收入3. 汇总计算单品总 ROIchannel_results {}total_orders 0total_revenue 0.0total_ad_spend 0.0total_production 0.0for cb in channel_budgets:fr self.calculate_channel_roi(cb, product)channel_results[cb.channel.value] {orders: fr.orders,valid_orders: fr.valid_orders,revenue: fr.revenue,ad_spend: fr.ad_spend,production_cost: fr.production_cost,roi: fr.roi,ctr: fr.clicks / fr.impressions if fr.impressions 0 else 0,cvr: fr.orders / fr.clicks if fr.clicks 0 else 0,}total_orders fr.valid_orderstotal_revenue fr.revenuetotal_ad_spend fr.ad_spendtotal_production fr.production_cost# 产品成本按有效成交计算product_cost_total product.cost * total_orderslogistics_total product.logistics_cost * total_orderstotal_cost total_ad_spend total_production product_cost_total logistics_totalnet_profit total_revenue - total_costroi (total_revenue - total_ad_spend - total_production) / total_cost \if total_cost 0 else 0.0return ProductRoiResult(product_idproduct.product_id,product_nameproduct.name,channel_breakdownchannel_results,total_revenuetotal_revenue,total_costtotal_cost,total_profitnet_profit,roiroi,orderstotal_orders,)def rank_products(self,products: List[Product],channel_budgets: List[ChannelBudget],) - List[ProductRoiResult]:多单品 ROI 排名results []for p in products:r self.calculate_product_roi(p, channel_budgets)results.append(r)# 按 ROI 排序results.sort(keylambda x: x.roi, reverseTrue)# 标注排名for i, r in enumerate(results):r.rank i 1return resultsmarginal_analyzer.py#!/usr/bin/env python3# -*- coding: utf-8 -*-marginal_analyzer.py边际效用分析器分析多投 1 万块多赚多少from typing import Dict, Listfrom data_models import Product, ChannelBudget, FunnelResultfrom funnel_model import FunnelModelfrom config import MARGINAL_PARAMSimport numpy as npclass MarginalAnalyzer:边际效用分析器核心问题已经投了 X 万再投 1 万ROI 是升还是降def __init__(self, product: Product):self.product productself.saturation_k MARGINAL_PARAMS[saturation_k]def analyze(self,channel: str,current_budget: float,production_cost: float 0.0,step: float 10000.0,max_budget: float 500000.0,) - List[Dict]:边际 ROI 分析Returns:每个预算水平下的边际 ROIfrom config import CHANNEL_PARAMSchannel_type Nonefor ct, params in CHANNEL_PARAMS.items():if params[name] channel or ct channel:channel_type ctbreakif channel_type is None:raise ValueError(f未知渠道: {chan利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛