尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

内衣零售数据系统实战:Python打造销售可视化与销量预测全链路

内衣零售数据系统实战:Python打造销售可视化与销量预测全链路 1. 内衣零售的数据盲区以及我为什么要搭这套系统先讲个真实场景。去年年中我参与了一个时尚内衣品牌的数字化转型项目对方门店运营负责人开会时拍出一张Excel表说这是上个月的销售汇总里面每个款式的颜色SIZE组合有上百个字段区域经理们为了核对报表要花两三天而且各个渠道的退货口径还不一样。最要命的是补货全靠店长拍脑袋判断热门款断码三五天补不上滞销款压了一仓库的货。这个场景在很多服装零售项目里太典型了。但我做数据工作多年第一反应是内衣这个品类比普通服装更适合用数据系统去管。原因在于它的商品结构极其吃数据——一件文胸通常有罩杯、围度两个维度组合出来SKU数量是普通T恤的十几倍同时它的复购率高、尺码敏感度高、退换货频次高任何一个环节靠人工经验都管不过来。所以当对方提出要做一套销售数据可视化销量预测的系统时我判断这不是为了赶时髦上大屏而是业务真的被数据问题卡住了。这个项目最终的技术路线很朴素Python做全链路数据处理SQLite/MySQL存明细和汇总Flask提供接口ECharts渲染前端看板预测部分先上统计基线模型再跑LightGBM做升级。没有Hadoop、没有Spark整个过程走了大概三个月。这套东西的价值不在于用上了多少大数据技术名词而在于把价格、库存、尺码、区域、会员这些分散的数据真正串成了一条能被决策使用的链路。这篇文章我打算完整复盘一遍这个系统的分析与落地过程。适合三类人看一是正在做服装/零售类数据项目的同学想找一个能直接参考的方案二是打算用Python做数据可视化和预测系统但不想一上来就堆太重的大数据框架的人三是业务侧的数据产品经理想了解一套系统从数据清洗到模型落地的完整链路到底是什么样。先说清楚这篇文章不是讲算法论文也不是讲平台架构就是讲一套能跑起来、能出报表、能辅助补货的真实系统是怎么一步步做出来的。1.1 一个SKU裂变的行业困境内衣品类最大的数据难题是SKU维度爆炸。普通一件T恤可能就颜色尺码两个维度S/M/L/XL一共四个码。但一件文胸要有罩杯A/B/C/D/E甚至更多和底围70/75/80/85两个独立维度一个款式轻松裂变出20~30个SKU。如果再叠加颜色动不动就上百个SKU。内衣品牌的仓库里躺着的不是一个款一个码而是成百上千个款式-颜色-罩杯-底围的组合。这意味着什么意味着同样的销售总金额内衣服饰的库存管理粒度要细得多。我在项目里统计过该品牌一个季度在售SKU有8000多个但其中约15%的SKU贡献了70%以上的销售额大量长尾SKU既占库存又难卖。如果没有数据系统靠Excel无法在款式、颜色、尺码三个层级里同时做排名、对比和趋势分析更不要说发现哪些组合正在悄悄断货。所以这个项目的数据建模第一优先级不是炫技而是要把SKU维度拆清楚、把层级汇总关系理清楚。后面所有看板和模型都建立在这套维度模型之上。1.2 系统目标与边界不是搭平台是解决业务问题刚接手这个项目时有人提议直接用商业智能工具也有人提议搭一套完整的大数据平台。我最终都没有采纳。原因很实际商业智能工具确实能做看板但没法做预测模型而且面对8000多个SKU、多个渠道、多套尺码口径时清洗和建模能力太受限。反过来搭Hadoop/Spark这类大数据平台对这个项目来说又太重了——日均数据量也就几十万行级维护成本和人力要求完全超出一个小型数据团队的能力边界。我给系统定的边界只有三件事。第一把分散在POS系统、电商后台、会员系统里的订单数据汇总成统一可分析的销售事实表。第二基于汇总数据做全面可视化让运营看到趋势、结构、异常。第三用销量预测模型输出未来4周的销量预估直接服务补货决策。这三件事都做扎实比搞出一个华丽的标签体系靠谱得多。边界定清楚之后技术选型就顺理成章了数据量不大但计算逻辑复杂用Python的Pandas处理最舒服明细数据用SQLite/MySQL存方便查询可视化不重复造轮子直接用ECharts因为它的图表交互和文档生态都比较成熟预测模型用scikit-learn和LightGBM这两个库对中小数据规模的处理非常稳定。1.3 整体技术选型的核心思路我梳理一下最终的技术栈每个组件都对应一个具体问题环节工具解决的业务问题数据清洗与特征工程Python Pandas/NumPySKU口径统一、多源订单清洗、特征计算数据存储SQLite过渡/ MySQL正式明细与汇总分层存储、快速查询可视化后端Flask/FastAPI提供数据接口、看板数据聚合可视化前端ECharts销售趋势、SKU结构、门店对比等交互图表销量预测statsmodels LightGBM周维度销量预测、SKU层级补货建议调度Windows计划任务 / cron每日自动拉数、清洗、出报表这里有人会问为什么可视化不直接用Tableau或者Power BI我的回答是预测系统要和看板共用一套数据和逻辑完全用Python写能让数据流更透明而且业务方后续要加自定义指标时自己人能改不会被工具锁死。另外Python生态里的Pandas做数据透视和口径调整比拖拽式工具更可控。2. 全链路数据接入从POS流水到会员行为任何一个数据系统的地基都是数据接入。这个项目里数据源不算多但每一条都挺折腾。2.1 数据源盘点与字段设计项目接了四个数据源POS销售流水来自门店收银系统包含单号、SKU编码、数量、金额、门店、销售时间、导购员。这个数据最完整也是销售分析的基础。电商平台订单来自天猫/京东/抖音后台导出的订单明细包含商品ID、SKU属性、实付金额、收货城市、下单时间、退款状态。注意电商订单和门店POS的口径完全不同后面专门讲。会员数据来自会员系统包含会员ID、性别、注册时间、最近购买时间。内衣品类会员复购价值很高后面做预测时要按会员维度拆解。库存数据来自WMS包含SKU的期初库存、入库、出库、期末库存。字段设计上我坚持用事实表维度表的方式建模订单事实表只保留粒度最细的一行一笔维度表分别维护产品、门店、日期、会员。这样设计的核心好处是任何指标都能在维度上自由组合不会出现导购员提成表和销售日报口径不一致这种老问题。2.2 清洗环节的三个重灾区尺码、渠道、时间清洗是整个项目里最花时间、最不性感但对结果影响最大的一步。三个重灾区必须单独处理。第一个是尺码口径统一。电商平台的SKU属性里写的是75B或B75门店POS里可能写的是底围75罩杯BExcel手工报表里甚至出现黑色75B这种混着颜色的写法。我的处理方式是把尺码拆成底围和罩杯两个独立字段然后统一成罩杯-底围的标准格式例如B75。这个拆解看起来小事但直接决定了后面能不能做尺码结构分析。没有这个统一断码分析就是一句空话。第二个是渠道口径差异。电商订单里有退款取消等状态而POS流水只有成交记录。如果不做过滤把未付款订单算进销售额整体预测会严重虚高。我的规则是线上只计算交易成功状态的订单退款单单独建一张退货表不混在销售表里。第三个是时间字段不一致。POS用的是本地服务器时间电商后台用的是服务器时间但有时区偏差统一成东八区并拆出年、月、周、星期字段。这里有一个经验一定要保留原始时间戳别清洗完就把原字段删了后面排查数据异常时你会用它做对照。清洗的代码其实不复杂核心就是多个CSV/Excel/Database源读进来统一字段名和值域再按主键做关联。用一个例子说明把零散的电商订单属性表转成标准SKU维度表大概长这样import pandas as pd # 读电商订单明细 df pd.read_excel(电商订单.xlsx) # 统一尺码把 罩杯B 底围75 - B75 def normalize_size(raw): if pd.isna(raw): return None raw str(raw).upper().strip() cup None band None # 常见格式75B、B75、75/B、罩杯B 底围75 for token in [罩杯, 底围, /, -, ]: raw raw.replace(token, ) # 数字90%情况是底围 digits .join([ch for ch in raw if ch.isdigit()]) letters .join([ch for ch in raw if ch.isalpha()]) if digits: band int(digits) if letters: cup letters return f{cup}{band} if cup and band else None df[std_size] df[规格].apply(normalize_size) df df[(df[订单状态] 交易成功) df[std_size].notna()] df.to_parquet(sales_clean.parquet)从实用角度我建议清洗脚本别追求一次到位而是做成可重入的脚本每次运行先全量刷一遍源数据再增量追加。这样源头字段一旦新增或变化重跑即可不用维护复杂的断点状态。2.3 用Python做增量抽取和任务调度数据接入不能每天靠人手动导出上传必须做成自动化任务。我的做法是写一套简单的增量抽取脚本核心逻辑是利用更新时间字段做增量标记。思路是这样每张源表都维护一个last_updated时间戳首次全量拉取后下次只拉取时间大于上次标记的数据。这个方案比单纯用主键ID增量更稳因为订单改价、退货状态变更都发生在更新时间上。调度上我一开始用cron后来项目部署在Windows服务器上就换成了计划任务。坦白讲Python生态里成熟的调度框架很多但小项目不需要上Airflow用一个入口脚本加任务列表就够了。这里有个非常重要的经验一定要在每天数据清洗完成后生成一份数据质量报告包括源表行数、缺失值数量、新增SKU数量、销售额汇总与手工报表的差异值。数据质量报告是后面排查问题的关键依据我见过太多项目上线后数据对不上却没有任何日志能定位是哪天开始出问题的。2.4 为什么没有直接上Hadoop数据量级与维护成本关于大数据这个概念我必须泼点冷水。项目上线时我统计过全渠道月订单量在50万~80万行之间加上明细行、库存快照、会员行为一年累计数据量大概几千万行。这个量级MySQL单表都能扛住用Pandas处理也就是几十秒的事情。如果这时候上Hadoop集群假设三台机器硬件成本、运维成本、学习成本加起来比实际业务收益高得多。那这个项目算大数据吗我的看法是大数据不仅指数据量更指数据的复杂度和维度。内衣这个品类SKU维度多、渠道多、尺码组合多这种维度爆炸带来的复杂度恰恰是大数据技术最擅长解决的问题——只不过解决手段不一定要用分布式框架用Python的单机生态配合合理的建模设计也能做得很好。选型这件事我用一个原则先算清楚数据量和计算量再选工具而不是先定工具再找理由。一行明明白白的计算胜过一堆炫技的架构图。3. 可视化系统的搭建ECharts Flask SQLite可视化是这个项目里业务方感知最强的一部分。运营每天打开看板看的数据就是销售、库存、结构、异常。做这套可视化我最大的心得是图表数量不重要业务能看懂并作出行动才重要。3.1 架构概况整个可视化链路是SQLite中的汇总表 → Flask接口 → 前端ECharts图表。具体来说后端按看板需要的维度预先用SQL或Pandas做好聚合生成按日期、按周、按SKU、按门店的汇总表Flask只做读操作把汇总数据以JSON格式返回前端用ECharts渲染。这个架构的好处是接口薄、逻辑简单、调试快。前端收到JSON直接画图后端不用为每种图表单独写接口而是设计一个通用的聚合接口app.route(/api/sales/summary) def sales_summary(): dim request.args.get(dim, week) # week / brand / store start request.args.get(start) end request.args.get(end) df pd.read_sql(select * from fact_sales where 11, engine) # 过滤 df df[(df[date] start) (df[date] end)] # 聚合 if dim brand: g df.groupby(brand)[sales_amount].sum().reset_index() return g.to_dict(orientrecords) elif dim store: g df.groupby(store_name)[sales_amount].sum().reset_index() return g.to_dict(orientrecords) # 默认按周 df[week] pd.to_datetime(df[date]).dt.isocalendar().week g df.groupby(week)[sales_amount].sum().reset_index() return g.to_dict(orientrecords)从业务使用的角度看我不建议一开始就做几十个图表。先用最核心的五个图表解决80%的问题等运营习惯了再逐步增加维度下钻。3.2 核心看板一销售概览与品类结构销售概览看板回答三个问题整体卖得怎么样哪个品类在涨哪个颜色/风格在变我用四个图来做——总销售额趋势线、品类占比饼图、新老品销售额对比、颜色偏好排行柱状图。这些图都挂在同一个时间筛选器上运营可以选择本周/本月/本季快速切换。有一个细节内衣品类的品类不能只分文胸、内裤、家居服。我在做品类标签时把文胸又拆成了无钢圈、“有钢圈、运动型三个子类因为无钢圈的销售增速和利润结构跟有钢圈完全不同。看板里加了这个维度后买手团队直接在图上看到无钢圈在华东区域占比已经超过45%这个结论马上决定调整新品引进比例。3.3 核心看板二尺码-款式矩阵发现断码与滞销这是整套可视化系统里业务方觉得最实用的一个图表尺码-款式热力矩阵。矩阵横轴是款式纵轴是尺码颜色深浅代表该组合的实际销量占库存的比重。颜色偏红说明销量高但库存可能不足颜色偏蓝说明有货但卖不动。这个图能直接暴露下面几类问题某款式整体卖得好但个别尺码缺货红色出现在某个小格子。某款式库存一大堆但几乎所有尺码都在滞销整行蓝色。某个尺码系列全渠道都断货说明供应链或尺码订货结构有问题。我用ECharts的heatmap实现行和列都能通过鼠标悬停看到具体数据。这个图看起来简单但关键在背后数据模型的细粒度——必须先保证每个SKU的销量和库存都按款式-尺码拆得足够细否则画不出这个矩阵。这一步做扎实了补货人员的工作从逐个翻表变成看图下结论。3.4 核心看板三区域与门店对比门店对比看板我用的是地图柱状图组合。地图展示各省份销售额点击省份时下钻到该省门店列表右侧柱状图显示门店销售额排名、同比变化、坪效每平米销售额等等。这里我踩过的一个坑是门店对比如果只比销售额会产生严重误导。A门店面积200平卖50万B门店面积80平卖45万表面看A更好实际坪效B完胜。所以我在看板里同时放了三档指标销售额、坪效、同比增速。运营可以通过切换指标从不同角度评估门店质量。另外内衣门店的尺码结构差异很大华东门店的B罩杯/75底围销量占比高华北门店明显偏好更大的罩杯。这个现象在数据里非常清楚如果补货时按全国统一结构调配必然导致部分区域断码。门店对比看板加了一张区域尺码偏好对比图之后区域调货的负责人第一次有了数据依据。3.5 交互细节下钻与联动可视化系统做得好不好评估标准从来不是大屏是否炫而是能不能在3分钟内找到问题答案。为此我在交互上做了几个设计所有图表共享同一个时间维度筛选器选时间段全局联动。单击饼图品类进入该品类的SKU排行榜双击排行进入单SKU的销售明细页。所有金额指标支持销售额/销量/毛利三种口径切换。前端实现上ECharts有一个dispatchAction的API可以用来触发图表间的联动。比如点击地图上的省份时触发柱状图的数据更新。具体做法就是维护一个全局状态对象任何图表交互都先更新状态再让所有图表重新拉数据// 全局状态 let globalFilter { start: 2025-01-01, end: 2025-03-31, region: , store: }; // 地图点击事件 myChart.on(click, function(params) { globalFilter.region params.name; refreshAllCharts(); }); function refreshAllCharts() { salesTrendChart.loadData(globalFilter); skuMatrixChart.loadData(globalFilter); storeRankChart.loadData(globalFilter); }这个设计的核心价值在于它不是给领导欣赏的大屏而是给运营日常使用的工具。系统上线后区域经理们最快的一个反馈是我不用再等财务部下班后甩给我Excel了。4. 销量预测系统的建模过程预测这个环节是整套系统里技术含量最高、也是最容易翻车的地方。我见过很多人一上来就上LSTM结果预测效果不如简单方法。这次项目我采用的策略是先用统计基线模型跑通流程再逐步升级到机器学习模型每一步都跟业务方对比验证。4.1 预测目标定义到底在预测什么做预测前最重要的不是选算法而是定义清楚预测目标。经过和运营反复讨论我们把目标定为未来4周各SKU周销量的预测。为什么不预测日销量因为内衣的日销量波动非常大促销、天气、节假日都可能导致单日暴增或暴跌这种波动不是模型能捕捉的预测日销量误差大到没有业务意义。而周销量天然平滑了短期波动补货周期本身也是按周来算的。第二个要定义的是粒度。是在品牌级别预测还是SKU级别预测我的经验是预测要分两级做。集团和品类层面预测的销售额用于制定整体目标SKU层面预测的销量用于补货。这两级模型不能用一个因为SKU级别的预测噪音太大但如果只做SKU预测再汇总全局误差反而会互相抵消一部分。4.2 基线模型季节系数 门店权重我选择的最先落地的模型不是机器学习而是业内零售预测最经典的季节系数法也叫比例预测法。核心思路是用过去12周的周销量平均值乘以季节系数再按门店权重分摊。公式如下预测销量 历史周均销量 × 季节系数 × 渠道/门店权重 季节系数 去年同期四周销量 / 去年同期全年四周日均销量这个模型有两个好处第一完全可解释运营能看懂为什么预测这个数第二实施成本极低只要数据表里有过去52周的销量就能算出来。我当时的实现是这样def seasonal_forecast(df, sku, horizon_weeks4): df: DataFrame, 包含date, sku, sales_qty sku_df df[df[sku] sku].copy() sku_df[week] pd.to_datetime(sku_df[date]).dt.isocalendar().week sku_df[year] pd.to_datetime(sku_df[date]).dt.isocalendar().year # 历史周均 recent sku_df[sku_df[date] pd.Timestamp.today() - pd.Timedelta(weeks12)] avg_sales recent[sales_qty].mean() # 季节系数去年同期的周销售 / 去年整体周均 last_year sku_df[sku_df[year] sku_df[year].max() - 1] if len(last_year) 0: base last_year[sales_qty].mean() summer last_year[sales_qty].tail(4).mean() season_factor summer / base if base 0 else 1.0 else: season_factor 1.0 forecast avg_sales * season_factor return round(forecast, 2)基线模型上线后业务方评价是方向基本靠谱但有些SKU明显偏高有些偏低。这很正常因为模型没有捕捉促销活动和生命周期的影响。但它给了我一个很好的基准——后面升级模型时用基线模型的误差做对照能清楚知道新模型到底有没有变得更好。4.3 机器学习模型特征工程与实战基线跑通之后我决定升级到LightGBM。为什么选LightGBM而不是深度学习因为这个项目的数据量大概几百万行特征以表格结构化数据为主LightGBM在这种场景下训练快、效果稳定、调参友好而且不需要GPU。预测问题被我建模成一个监督学习回归问题给定一个SKU在某个周的上下文预测它未来4周的销量。特征工程是关键我用的特征分成四类第一类是历史销量统计特征包括过去1周、4周、8周、12周的销量、均值、标准差、增速。第二类是时间特征包括星期几、月中第几周、是否节假日附近。第三类是商品属性特征包括款式、类别、颜色数、尺码数、上市周数生命周期阶段。第四类是门店/渠道特征包括门店等级、区域、渠道占比。这里有一个很重要的经验SKU维度的预测数据量其实比较稀疏很多SKU周销量为0。如果直接建模模型会倾向于预测一个偏高的中间值。我的解决办法是分两步——第一步用分类模型预测这个SKU下周是否会有销量第二步才对有销量的SKU做回归预测最后把两步的结果相乘。LightGBM的部分代码import lightgbm as lgb from sklearn.model_selection import TimeSeriesSplit # 特征列 features [week_num, month, is_holiday, sku_age_weeks, sales_lag1, sales_lag4, sales_lag8, sales_mean_4w, store_level, channel_share] # 时间序列交叉验证 tscv TimeSeriesSplit(n_splits5) models [] for train_idx, val_idx in tscv.split(X): X_train, X_val X.iloc[train_idx], X.iloc[val_idx] y_train, y_val y.iloc[train_idx], y.iloc[val_idx] model lgb.LGBMRegressor( n_estimators500, learning_rate0.05, num_leaves31, subsample0.8, colsample_bytree0.8, random_state42 ) model.fit(X_train, y_train, eval_set[(X_val, y_val)], callbacks[lgb.early_stopping(50)]) models.append(model)用时间序列交叉验证而不是随机KFold是因为预测场景要求模型见到的是过去预测未来随机划分会导致严重的数据泄漏模型在验证集上表现虚高。另一个关键设计是分层建模热销SKU单独建模长尾SKU统一做一个小模型。因为热销SKU有充足的历史数据模型能学到趋势和季节性长尾SKU销量稀疏放在一起训练反而能利用其他SKU的共性信息。4.4 模型评估与业务翻译模型评估不能只看RMSE我引入了两个业务视角的指标第一个是覆盖准确率预测值落在实际值正负20%范围内的比例。这个指标业务方一听就懂直接对应预测准不准。第二个是方向命中率预测涨、实际也涨的比例。这对补货工作很有意义因为补货决策主要关心要不要多补方向对了比具体数值差一点更重要。最终LightGBM的预测误差大概比基线模型降低了15%~20%但并没有夸张到神预测的程度。我们做了一套预测可信度分级当模型置信度高的SKU给出补货建议置信度低的SKU仍然走人工判断。这个产品化设计比单纯追求模型精度更能被业务接受。我有一句很深的体会预测系统的落地难点从来不在模型精度而在于业务是否愿意相信模型。为了让业务方信任我们每周都把预测结果和实际值对比发到群里连续发了一个月让运营自己看到哪些品类预测得很准、哪些有偏差逐步建立了信任。5. 上线后的真实踩坑清单系统上线三个月踩过的坑比写代码时预想的多得多。挑几个最典型的分享出来希望后来者能绕开。5.1 数据对齐之痛口径不统一的连锁反应系统上线第一周运营就发现电商渠道的日销售额和平台后台导出的数据差了3万块。排查了很久最后发现三个原因平台后台显示的是下单金额我们统计的是成交金额未付款订单和退款单被混进去了平台节假日促销的优惠券分摊方式和我们不同部分跨店结算订单被重复计算。这个坑暴露了一个核心问题口径定义不只是在清洗时做一次而是要形成文档并且每次跑数据前都要检查。我在项目里做了一个配置表把所有指标的口径公式写进系统例如销售额成交订单的实付金额-退款金额每次报表生成时自动引用口径配置。这样改动口径时不用改代码只改配置表即可。5.2 预测模型的马后炮式失败上线第二周我们复盘预测效果发现某些SKU的预测误差特别大事后看原因一目了然那几周正好赶上两个大促活动而特征里没有促销信息。模型永远不会预知未来有什么活动所以这不是模型的错是特征工程少了关键维度。我的补救方案是只把确定性事件加入特征比如已知的促销排期、节假日日期把它们作为外部特征传入模型。至于临时追加的活动模型预测不准是正常的业务方需要靠活动调整系数机制来做人工干预。我在系统里加了一个活动因子输入框运营可以手动上调或下调某些品类的预测值这比让模型强行捕捉不确定性靠谱得多。5.3 可视化大屏好看不等于好用项目中期我做了一版很炫的大屏深色背景、动态光效、地图飞线领导看了很喜欢但运营用了两天就抱怨找不到我要看的数。原因是好看归好看信息密度低真正的决策信息淹没在视觉特效里。后来我把大屏推翻重做改成浅色简洁风格信息层级用大标题核心指标关联图表的方式组织。最重要的是每一个看板都绑定到一个决策问题上而不是堆积图表数量。比如库存健康看板只展示断码率、滞销库存占比、周转天数三个指标每个指标配一个说明文字。改版后运营每天主动打开的频次明显提高。5.4 给后来者的实施建议最后总结几条实操层面的经验说得直白一点第一数据清洗的时间至少占整个项目40%这不是浪费而是必须。没有干净的数据后面所有算法和图表都是空中楼阁。第二先做可视化再做预测。可视化能让业务方看到数据和价值建立信任预测模型才有落地的土壤。反过来先上模型业务方会把你当成一个算命的预期错位。第三预测结果一定要输出置信度并且允许人工修正。系统不是用来替代人的而是帮人做决策的工具这个定位决定了它的推广难度。第四如果想参考这个项目建议用我上周推荐的技术栈做最小版本。先用PandasFlaskECharts跑通清洗、看板、简单预测再逐步加复杂度。不要一开始就做微服务、消息队列、分布式计算那些在这个场景里只会拖慢你的进度。这个项目最让我有成就感的部分不是模型精度提高了多少而是看到补货人员第一次能在周一早上直接打开系统看建议补货名单而不是像以前一样躲在小房间里翻Excel到中午。数据系统对业务的价值往往就体现在这种具体的、日常的、没有戏剧性的改变里。
返回列表