day-045-Matplotlib综合实战

📅 2026/7/28 21:01:27 👁️ 阅读次数 📝 编程学习
day-045-Matplotlib综合实战

Day 45:Matplotlib 综合实战——制作数据分析报告

Matplotlib 最后一课。从原始数据到一份"拿得出手"的多页数据报告——4张风格统一的专业图表,覆盖趋势、对比、分布、关联四个分析维度。


一、项目背景

你是一家连锁咖啡店的区域经理,手上有 12 个月的销售数据。现在需要做一份月度汇报。

分析目标

  1. 📈 趋势——月度销售额变化
  2. 📊 对比——各门店/产品表现
  3. 📦 分布——顾客消费金额分布
  4. 🔗 关联——气温与销量的关系
importmatplotlib.pyplotaspltimportnumpyasnpimportpandasaspd# === 全局设置 ===plt.rcParams.update({"font.sans-serif":["SimHei","Microsoft YaHei"],"axes.unicode_minus":False,"figure.dpi":120,"axes.grid":True,"grid.alpha":0.3,"font.size":11,})# === 统一配色 ===COLORS={"primary":"#2C3E50","accent1":"#E74C3C","accent2":"#3498DB","accent3":"#2ECC71","accent4":"#F39C12","accent5":"#9B59B6","palette":["#E74C3C","#3498DB","#2ECC71","#F39C12","#9B59B6","#1ABC9C"],}

二、数据准备

np.random.seed(42)# === 生成模拟数据 ===months=np.arange(1,13)month_labels=[f"{m}月"forminmonths]# 月销售数据(有季节性:夏秋高峰)base=500seasonal=200*np.sin((months-3)*np.pi/6)trend_growth=np.linspace(0,150,12)noise=np.random.normal(0,30,12)monthly_revenue=base+seasonal+trend_growth+noise# 各门店数据stores=["朝阳店","海淀店","西城店","东城店","丰台店"]store_revenue=[285,320,210,265,180]# 各产品数据products=["拿铁","美式","摩卡","冷萃","茶饮","糕点"]product_revenue=[420,310,250,220,180,140]# 顾客单次消费金额(模拟偏态分布)np.random.seed(42)customer_spending=np.concatenate([np.random.normal(25,8,600),# 主流客群np.random.normal(50,15,300),# 中高消费np.random.normal(80,20,100),# 高消费])# 气温与销量(每日数据)temps=np.linspace(-5,38,365)# 全年气温变化ice_drink_sales=np.clip(50+8*(temps-10)+np.random.normal(0,15,365),0,400)hot_drink_sales=np.clip(300-6*(temps-5)+np.random.normal(0,20,365),0,400)

三、图表1:月度趋势(双Y轴组合图)

# 计算环比增长率mom_growth=[None]+[(monthly_revenue[i]-monthly_revenue[i-1])/monthly_revenue[i-1]*100foriinrange(1,12)]fig,ax1=plt.subplots(figsize=(12,5))# 柱状图——销售额bars=ax1.bar(months,monthly_revenue,width=0.6,color=COLORS["accent2"],alpha=0.75,label="销售额",edgecolor="white",zorder=3)# 在柱子上标数值forbar,valinzip(bars,monthly_revenue):ax1.text(bar.get_x()+bar.get_width()/2,bar.get_height()+10,f"{val:.0f}",ha="center",fontsize=9,fontweight="bold")# 目标线target_line=np.full(12,600)ax1.plot(months,target_line,"--",color="gray",linewidth=1.5,alpha=0.7,label="目标线(600)")ax1.fill_between(months,monthly_revenue,target_line,where=(monthly_revenue>=target_line),color="green",alpha=0.1)ax1.fill_between(months,monthly_revenue,target_line,where=(monthly_revenue<target_line),color="red",alpha=0.1)ax1.set_xlabel("月份")ax1.set_ylabel("销售额(万元)",color=COLORS["accent2"])ax1.tick_params(axis="y",labelcolor=COLORS["accent2"])ax1.set_xticks(months)ax1.set_xticklabels(month_labels)ax1.set_ylim(0,900)# 右Y轴——环比增长率ax2=ax1.twinx()valid_months=months[1:]valid_growth=[gforginmom_growthifgisnotNone]ax2.plot(valid_months,valid_growth,"o-",color=COLORS["accent1"],linewidth=2,markersize=8,label="环比增长率",zorder=4)ax2.set_ylabel("环比增长率(%)",color=COLORS["accent1"])ax2.tick_params(axis="y",labelcolor=COLORS["accent1"])ax2.axhline(y=0,color=COLORS["accent1"],linestyle="--",alpha=0.4)# 合并图例lines1,labels1=ax1.get_legend_handles_labels()lines2,labels2=ax2.get_legend_handles_labels()ax1.legend(lines1+lines2,labels1+labels2,loc="upper left",fontsize=9)# 去掉多余边框ax1.spines["top"].set_visible(False)ax1.set_title("连锁咖啡店月度销售额与环比增长",fontsize=15,fontweight="bold",pad=15)plt.tight_layout()plt.savefig("report_01_monthly_trend.png",dpi=150,bbox_inches="tight")plt.show()

四、图表2:门店 × 产品对比(堆叠柱状图)

# 模拟各门店各产品销售额np.random.seed(42)store_product_data={}forstoreinstores:store_product_data[store]=[np.random.randint(30,120)for_inproducts]df_sp=pd.DataFrame(store_product_data,index=products)fig,axes=plt.subplots(1,2,figsize=(14,5))# --- 左:门店总销售额排名 ---sorted_stores=sorted(store_revenue,reverse=True)sorted_names=[sfor_,sinsorted(zip(store_revenue,stores),reverse=True)]bars_h=axes[0].barh(range(len(sorted_names)),sorted_stores,color=COLORS["palette"][:len(stores)],edgecolor="white",height=0.6)axes[0].set_yticks(range(len(sorted_names)))axes[0].set_yticklabels(sorted_names,fontsize=11)axes[0].set_xlabel("销售额(万元)")axes[0].set_title("各门店总销售额排名",fontsize=13,fontweight="bold")# 标数值和排名fori,(bar,val)inenumerate(zip(bars_h,sorted_stores)):axes[0].text(val+3,i,f"#{i+1}{val}万",va="center",fontsize=10,fontweight="bold")# --- 右:产品堆叠柱状图 ---x=np.arange(len(stores))bottom=np.zeros(len(stores))fori,(product,color)inenumerate(zip(products,COLORS["palette"])):values=df_sp.loc[product].values axes[1].bar(x,values,bottom=bottom,label=product,color=color,edgecolor="white",linewidth=0.5)bottom+=values axes[1].set_xticks(x)axes[1].set_xticklabels(stores)axes[1].set_ylabel("销售额(万元)")axes[1].set_title("各门店产品销售构成",fontsize=13,fontweight="bold")axes[1].legend(loc="upper right",fontsize=8,ncol=2)plt.suptitle("门店与产品分析",fontsize=15,fontweight="bold")plt.tight_layout()plt.savefig("report_02_store_product.png",dpi=150,bbox_inches="tight")plt.show()

五、图表3:消费金额分布(直方图+累积曲线)

fig,ax1=plt.subplots(figsize=(10,5))# 直方图——消费金额分布counts,bins,patches=ax1.hist(customer_spending,bins=50,color=COLORS["accent2"],alpha=0.6,edgecolor="white",linewidth=0.3,label="频数")# 标记关键统计量mean_val=np.mean(customer_spending)median_val=np.median(customer_spending)ax1.axvline(mean_val,color=COLORS["accent1"],linestyle="--",linewidth=2,label=f"均值:{mean_val:.1f}元")ax1.axvline(median_val,color=COLORS["accent4"],linestyle="--",linewidth=2,label=f"中位数:{median_val:.1f}元")ax1.set_xlabel("单次消费金额(元)")ax1.set_ylabel("顾客数量",color=COLORS["accent2"])ax1.tick_params(axis="y",labelcolor=COLORS["accent2"])ax1.set_title("顾客单次消费金额分布",fontsize=14,fontweight="bold")# 右Y轴——累积百分比曲线ax2=ax1.twinx()sorted_data=np.sort(customer_spending)cumulative_pct=np.arange(1,len(sorted_data)+1)/len(sorted_data)*100ax2.plot(sorted_data,cumulative_pct,color=COLORS["accent3"],linewidth=2,label="累积百分比")ax2.set_ylabel("累积百分比(%)",color=COLORS["accent3"])ax2.tick_params(axis="y",labelcolor=COLORS["accent3"])# 合并图例并添加洞察文字lines1,labels1=ax1.get_legend_handles_labels()lines2,labels2=ax2.get_legend_handles_labels()pct_below_50=(customer_spending<=50).sum()/len(customer_spending)*100ax1.text(0.95,0.85,f"{pct_below_50:.0f}% 的顾客\n消费不超过50元",transform=ax1.transAxes,ha="right",fontsize=10,bbox=dict(boxstyle="round",facecolor="lightyellow",alpha=0.8))ax1.legend(lines1+lines2,labels1+labels2,loc="upper right")plt.tight_layout()plt.savefig("report_03_spending_dist.png",dpi=150,bbox_inches="tight")plt.show()

六、图表4:气温 vs 冷热饮销量(散点+趋势)

fig,ax=plt.subplots(figsize=(10,5))# 散点图sc1=ax.scatter(temps,ice_drink_sales,c="#3498DB",s=15,alpha=0.5,label="冷饮",edgecolor="none")sc2=ax.scatter(temps,hot_drink_sales,c="#E74C3C",s=15,alpha=0.5,label="热饮",edgecolor="none")# 趋势线(多项式拟合)fordata,color,labelin[(ice_drink_sales,"#1A5276","冷饮趋势"),(hot_drink_sales,"#7B241C","热饮趋势"),]:z=np.polyfit(temps,data,2)p=np.poly1d(z)sorted_temps=np.sort(temps)ax.plot(sorted_temps,p(sorted_temps),color=color,linewidth=2.5,label=label)# 标注交叉点(冷热饮销量相等的大致温度)cross_temp=20ax.axvline(cross_temp,color="gray",linestyle=":",linewidth=1.5)ax.annotate(f"约{cross_temp}°C\n冷热饮销量持平",xy=(cross_temp,200),xytext=(cross_temp+8,300),arrowprops=dict(arrowstyle="->",color="gray",lw=1.5),fontsize=10,bbox=dict(boxstyle="round",facecolor="white",alpha=0.8))ax.set_xlabel("气温(°C)",fontsize=12)ax.set_ylabel("日销量(杯)",fontsize=12)ax.set_title("气温对冷热饮品销量的影响",fontsize=14,fontweight="bold")ax.legend(loc="best",fontsize=9)plt.tight_layout()plt.savefig("report_04_temp_sales.png",dpi=150,bbox_inches="tight")plt.show()

七、Matplotlib 五天回顾

天数内容核心技能
Day 41Matplotlib 入门基本图表类型、中文显示、Figure/Axes 概念
Day 42坐标轴与图例刻度定制、双Y轴、legend、annotate、spines
Day 43颜色与子图colormap、imshow、subplot_mosaic、fill_between
Day 44高级图表箱线图、小提琴图、hexbin、3D 绘图
Day 45综合实战完整分析报告、四张专业图表、保存输出

八、第二阶段回顾 & 明日预告

第二阶段(Day 31-50)进度:

NumPy(5天) ████████████████████████ ✅ 完成 Pandas(5天) ████████████████████████ ✅ 完成 Matplotlib(5天)████████████████████████ ✅ 完成 数据分析实战(5天) ░░░░░░░░░░░░░░░░░░░░░░░░░░ 👈 明天开始

明天进入第二阶段最后一环——实战数据分析项目(Day 46-50),用 5 天完整走一个真实数据分析流程:爬取数据 → 清洗 → 探索 → 可视化 → 写报告。


5 天 Matplotlib,从画第一根线到做出四张专业图表。你现在已经能把任何数据变成有说服力的视觉故事。

第45天,打卡完成!🏆


本系列是个人学习笔记,如有错误欢迎在评论区指正交流。