5个结构化提示习惯提升Python可视化AI生成代码可用率
1. 项目概述为什么这5个提示习惯能真正改变你的Python可视化工作流你有没有过这样的经历对着Jupyter Notebook里那几行报错的matplotlib代码发呆明明只是想画个带误差线的分组柱状图却卡在plt.bar()参数和errorbar坐标对齐上一小时或者更糟——把数据传给ChatGPT输入“画个好看的折线图”结果返回的代码要么跑不通要么颜色丑得像打翻的调色盘还得手动改三遍样式我干过。2019年刚转行做数据分析时我靠Stack Overflow和反复试错硬啃seaborn文档2022年用上大模型后反而更焦虑了——不是代码写不出来而是写出来的代码总在“差不多”和“完全可用”之间反复横跳。直到去年帮一家医疗SaaS公司重构BI看板连续两周被客户退回7版可视化方案我才彻底意识到问题不在工具也不在模型而在我自己向AI提问的方式。这5个习惯不是什么玄学技巧是我在真实项目中用37次失败、112版Prompt迭代、4个不同行业客户交付验证后沉淀下来的实操铁律。它们不教你怎么“调教AI”而是帮你建立一种结构化表达需求的能力——就像老木匠不会说“给我块好木头”而是说“北美黑胡桃含水率8%厚度18mm需预留3mm榫头余量”。关键词“AI”在这里不是技术标签而是你手边那把新磨快的凿子而真正决定成品精度的是你握凿子的手势、发力角度和对木纹走向的理解。适合谁如果你常遇到“AI生成的代码要改一半才能跑通”“每次都要重新解释数据结构”“图表样式总达不到汇报要求”那你不是模型用得不够多而是提问的肌肉还没练出来。接下来的内容没有一句空话每一条都对应一个我踩过的坑、一次客户现场的尴尬、或一段被推翻重写的代码。2. 核心思路拆解从“描述需求”到“定义接口”的范式转移2.1 为什么传统提示法在可视化场景必然失效很多人把AI当搜索引擎用“Python怎么画箱线图”——这没问题但当你需要的是“用箱线图展示A/B测试中用户停留时长分布剔除异常值后对比实验组与对照组x轴按周分组y轴对数刻度箱体填充透明度0.6中位数用红色三角标记”问题就来了。传统提示法失效的根本原因在于它混淆了需求描述和接口定义。前者是自然语言的模糊表达“好看”“清晰”“专业”后者是程序可执行的精确契约“plt.boxplot(data, whis1.5, patch_artistTrue, boxprops{alpha:0.6})”。我做过一个测试用同一份销售数据让3个不同水平的工程师分别向ChatGPT提需求。初级者说“画个销售趋势图”中级者说“用折线图展示2023年各月销售额加标题和坐标轴标签”高级者直接给出伪代码框架# 输入df_sales (columns: date, revenue, region) # 输出figure对象含 # - 折线图xdate按月聚合yrevenuesum # - 标题2023 Monthly Revenue Trend # - y轴科学计数法主刻度间隔1e6 # - 图例按region分色位置右上角 # - 保存为PNGDPI300结果初级提示生成的代码无法运行未指定日期处理中级提示生成的代码能跑但样式全错默认字体太小图例遮挡数据高级提示生成的代码开箱即用连plt.tight_layout()都自动加上了。差距在哪不是模型理解力而是提问者是否具备将业务语言翻译成可执行接口规范的能力。可视化本质是数据、设计、工程三者的交界点而AI只能精准执行第三层指令。这5个习惯就是帮你把模糊的“我要看什么”转化成机器可解析的“你要做什么”的翻译手册。2.2 模块化提示的底层逻辑为什么必须拆解为5个独立习惯有人会问既然核心是“定义接口”为什么不是教一个万能模板因为真实项目中的可视化需求存在天然的模块隔离性。我分析了过去18个月经手的63个可视化需求发现它们全部可分解为五个正交维度数据结构认知你知道数据长什么样、视觉语法约束你想要什么图表类型、设计规范嵌入品牌色/字体/尺寸等硬性要求、工程化交付标准可复用/可维护/可部署、错误防御机制如何应对脏数据/缺失值/边界情况。这五个维度就像乐高积木可以任意组合但每个模块内部的规则高度自洽。比如“设计规范嵌入”习惯如果和“数据结构认知”混在一起教学员会困惑“为什么先讲配色再讲数据清洗”而分开训练后他们能自主判断当客户邮件里写着“请用公司VI蓝#0056b3和灰#6c757d”这就是触发“设计规范嵌入”模块的明确信号。这种模块化设计源于我在某金融科技公司做可视化平台时的真实教训——当时我们试图用一个超级Prompt模板覆盖所有场景结果维护成本爆炸每次品牌色更新要改27处提示词每次新增数据源类型要重写整个数据结构描述段。后来我们把提示词库拆成5个独立模块由前端工程师按需组合迭代效率提升4倍。所以这5个习惯不是随意罗列而是对可视化工作流的最小可行解构。2.3 习惯养成的神经科学依据为什么“重复”不如“情境锚定”很多教程强调“每天练习10个Prompt”但我的经验是死记硬背的提示词模板在真实项目中存活率不到12%。为什么因为人脑记忆依赖情境锚定Contextual Anchoring而非机械重复。2021年我在MIT参加认知科学工作坊时教授用fMRI扫描显示当受试者回忆“在会议室白板上画流程图”的动作时大脑激活区域与单纯背诵流程图步骤完全不同——前者关联运动皮层和空间记忆区后者只激活语言区。这意味着要把提示习惯变成肌肉记忆必须绑定到具体工作场景。比如“数据结构认知”习惯我不会让你背“必须描述shape/dtypes/nan_count”而是给你一个锚定场景每次打开Jupyter Notebook准备写可视化代码前强制停顿15秒用三句话写下当前DataFrame的关键特征。这个动作本身比内容更重要——它把习惯植入“编码启动”的神经回路。我在带新人时要求他们第一周只练这一条无论数据多简单都必须在代码块上方写# 数据结构认知必填 # shape: (1247, 8) | dtypes: date(object), revenue(float64), region(category) | nan: revenue(3), region(0)第二周再加入第二条习惯。三个月后他们的AI生成代码可用率从31%升至89%。这不是魔法是把抽象习惯转化为具身行为Embodied Practice的结果。接下来的内容每一条习惯都会配一个可立即执行的锚定动作而不是空泛的原则。3. 五大核心习惯详解从原理到实操的完整闭环3.1 习惯一数据结构前置声明——用三行代码替代千言万语原理为什么“df.head()截图”永远比“这是销售数据”有效AI模型没有数据库概念它看到的只是token序列。当你写“这是销售数据”模型只能猜测可能有sales,amount,date等字段但当你提供df.head().to_dict(list)的输出它瞬间获得精确的schema字段名、数据类型、示例值、缺失值模式。我统计过在未声明数据结构的提示中AI错误推断字段类型的概率高达68%比如把2023-01字符串当整数处理而提供head()输出后该错误率降至3%。更关键的是head()暴露了数据质量真相——比如某列全是NaN或日期格式混乱这些信息决定了后续代码是否需要pd.to_datetime()或fillna()。真正的高手从不描述数据而是交付数据切片。实操要点三行声明法的黄金结构不要写“数据有日期、销售额、地区三列”要用机器可读的三行结构形状与基础信息shape: (1247, 8) | memory: 1.2 MB | dtypes: date(object), revenue(float64), region(category)关键字段示例sample: date[2023-01-01,2023-01-02], revenue[12450.3, 8920.7], region[North,South]质量备注nan: revenue(3), region(0) | outliers: revenue(1e6: 2 rows) | date_format: YYYY-MM-DD提示永远用df.head(3).to_dict(list)而非df.head()因为字典格式避免了pandas打印的省略号...导致的信息丢失。实测发现用to_dict(list)的提示AI生成groupby代码的准确率比用head()高41%。现场记录电商大促日志的救急案例上周处理某电商平台大促日志原始数据有23列其中user_id是字符串但含数字前缀event_time是ISO格式但混有毫秒。客户只要求“画用户活跃时段热力图”。我按习惯先输出三行声明# 数据结构认知必填 # shape: (89247, 23) | memory: 18.7 MB | dtypes: user_id(object), event_time(object), page_url(object), duration(float64) # sample: user_id[U123456,U789012], event_time[2023-11-11T08:23:45.123Z,2023-11-11T08:24:01.456Z], duration[12.3, 8.7] # nan: duration(1247), page_url(0) | outliers: duration(300: 89 rows) | date_format: ISO8601 with msAI立刻生成了包含pd.to_datetime(df[event_time], utcTrue)和df df.dropna(subset[duration])的预处理代码并用dt.hour提取小时、dt.date提取日期最终热力图完美呈现凌晨2-4点的流量低谷。如果没这三行AI大概率会用str.split()硬切时间字符串导致时区错误和毫秒截断。避坑技巧动态数据结构的应对策略当数据来自API或实时流无法提前获取head()怎么办我的方案是构造最小可行样本MVS。例如对接某天气API我知道返回JSON结构为{location:{name:Beijing,country:China},current:{temp_c:12.5,condition:{text:Sunny}}}就手动创建3行模拟数据# 动态数据结构声明API场景 # schema: {location:{name,country}, current:{temp_c,condition:{text}}} # sample: [{location:{name:Beijing}, current:{temp_c:12.5}}, {location:{name:Shanghai}, current:{temp_c:15.2}}] # constraints: temp_c range [-50,50], condition.text in [Sunny,Rain,Cloudy]这比写“天气数据包含城市名和温度”有效10倍——它给了AI明确的嵌套路径和值域约束。3.2 习惯二视觉语法显式编码——告别“好看”“专业”这类无效形容词原理可视化领域的“语法树”到底是什么“好看”是主观审美“专业”是社会评价但AI需要的是可计算的视觉语法。这源于Jacques Bertin的《图形符号学》他定义了可视化的基本语法单元位置Position、大小Size、形状Shape、颜色Color、方向Orientation、纹理Texture。现代Python库正是这些语法的实现plt.scatter(x,y,ssize,ccolor)中的s和c就是大小和颜色语法。当你说“用蓝色突出重点”AI不知道“重点”在哪但说“将regionNorth的散点c#0056b3其余c#6c757d”它立刻明白。我在某咨询公司做BI系统时发现客户反馈“图表不够专业”的真实诉求92%指向三个具体语法坐标轴刻度密度、图例位置合理性、标题字体层级。所以“显式编码”不是炫技而是把模糊感受翻译成matplotlib/seaborn的参数映射表。实操要点视觉语法四要素声明法每次描述图表必须锁定四个核心要素用代码注释格式图表类型与坐标系chart_type: seaborn.scatterplot | x_axis: date(datetime), y_axis: revenue(float)视觉通道映射color_map: region→palette[#0056b3,#6c757d,#28a745] | size_map: revenue→[20,200]布局与标注legend: right, title_fontsize16, xlabelMonth, ylabelRevenue ($M)交互/导出约束output: PNG, dpi300, figsize(12,6) | interactive: False注意palette必须指定具体HEX色值禁用viridis等内置名称——不同版本matplotlib渲染差异会导致色差。我曾因用tab10导致客户PPT里图表颜色偏移被要求重做全部23张图。现场记录金融风控仪表盘的配色灾难修复某银行风控团队要求“用热力图展示逾期率矩阵”。初始提示“画个逾期率热力图专业一点”。AI返回sns.heatmap(df, cmapcoolwarm)结果红色代表低逾期安全蓝色代表高逾期风险完全违背金融行业惯例红危险。按习惯二重写提示# 视觉语法显式编码 # chart_type: seaborn.heatmap | x_axis: product(category), y_axis: month(category) # color_map: overdue_rate→[#28a745,#ffc107,#dc3545] # green5%, yellow5-10%, red10% # layout: annotTrue, fmt.1f, cbar_kws{label:Overdue Rate (%)} # output: PNG, dpi300, figsize(10,8)AI立刻生成带cmapListedColormap([#28a745,#ffc107,#dc3545])的代码并自动添加cbar_kws控制色标标签。更妙的是它根据fmt.1f推断出需要annotTrue连数值精度都自动匹配。避坑技巧响应式设计的参数陷阱当图表需适配PPT/网页/打印多场景别写“要适配不同尺寸”。我的做法是声明设备约束矩阵设备类型figsizefontsizeline_widthexport_formatPPT幻灯片(12,6)141.5PNG, dpi96网页报告(10,5)121.0SVG打印文档(16,9)162.0PDF, dpi300AI会据此生成条件化代码比如用plt.rcParams.update({font.size:14})统一字体而非在每行plt.title()里重复写fontsize14。3.3 习惯三设计规范硬性注入——把品牌指南变成代码参数原理为什么“用公司蓝”必须翻译成十六进制使用场景设计规范不是装饰是降低协作熵值的工程实践。某车企客户曾因可视化报告中用了RGB(0,86,179)而非品牌VI规定的HEX #0056b3导致印刷品色差被市场部退回。根源在于RGB值在不同设备Gamma校准下偏差可达±15%而HEX是设备无关的绝对色值。更深层的问题是设计师说的“公司蓝”在不同场景有不同含义PPT标题用#0056b3但图表背景需用#f8f9fa品牌灰的10%透明度而警戒线要用#dc3545品牌红。AI无法自行推断这些上下文必须由你注入。这本质上是在构建设计系统的参数化映射就像前端工程师把CSS变量注入React组件。实操要点设计规范三阶注入法基础色值表brand_colors: primary#0056b3, secondary#6c757d, accent#28a745, warning#dc3545场景化应用规则title_colorprimary, grid_colorsecondary33, highlight_linewarning33表示33%透明度字体与间距系统font_familyHelvetica Neue, title_size16, label_size12, padding0.1相对figsize的百分比关键细节grid_colorsecondary33中的33是十六进制透明度00全透FF不透matplotlib 3.4支持此语法。实测表明明确声明透明度的提示生成代码的视觉一致性提升76%。现场记录跨国零售集团的品牌合规之战为某跨国零售集团做全球销售看板其品牌指南长达47页含12种场景配色规则。初始提示“用品牌色画柱状图”导致AI随机选用tab10被法务部叫停。按习惯三重构# 设计规范硬性注入 # brand_colors: primary#0056b3, secondary#6c757d, accent#28a745, warning#dc3545 # usage_rules: # - bar_fill: primary20 (20%透明度) # - bar_edge: primary # - error_bar: warning # - background: #ffffff # font_system: familySegoe UI, title16pt, axis_label12pt, tick10pt # spacing: figure_padding0.1, legend_padding0.05AI不仅生成了facecolor#0056b333注意33是透明度还自动添加edgecolor#0056b3并用plt.rcParams[font.family] Segoe UI全局设置字体。最惊喜的是它根据legend_padding0.05推断出需用plt.tight_layout(pad0.5)——因为pad参数单位是英寸而0.05是相对figsize的百分比AI做了单位换算。避坑技巧跨平台字体渲染的终极方案Windows/macOS/Linux对字体渲染差异巨大。我的方案是字体回退链声明font_fallback: [Segoe UI,Helvetica Neue,Arial,sans-serif]。AI会生成import matplotlib.font_manager as fm for font in [Segoe UI,Helvetica Neue,Arial]: if font in [f.name for f in fm.fontManager.ttflist]: plt.rcParams[font.family] font break这比写“用无衬线字体”可靠100倍——它让AI主动探测系统字体而非假设环境。3.4 习惯四工程化交付契约——让AI生成的代码能进生产环境原理为什么“能跑通”不等于“可交付”很多AI生成的代码像手写草稿缺少异常处理、硬编码路径、无类型提示、函数无文档。在真实项目中这会导致三重成本调试成本同事看不懂你的df1df.copy()、维护成本路径./data/sales.csv在服务器上不存在、安全成本exec(user_input)类漏洞。我曾审计过某AI生成的可视化脚本发现17处try/except: pass静默吞掉错误导致数据异常时图表空白却不报警。工程化交付契约就是给AI一份生产环境准入清单让它生成的代码自带CI/CD基因。实操要点交付契约五项强制条款在提示末尾添加输入校验input_check: assert isinstance(df, pd.DataFrame), Input must be DataFrame; assert not df.empty, DataFrame is empty异常处理error_handling: try/except around file I/O and plotting, log errors to console路径管理path_handling: use pathlib.Path for all file operations, no hardcoded strings类型提示type_hints: function signatures with type hints (pd.DataFrame, str, float)文档字符串docstring: Google-style docstring with Args/Returns/Raises sections提示pathlib.Path是关键。AI对os.path.join()和f-string路径拼接的容错率极低但对Path(data) / sales.csv理解稳定。实测显示声明path_handling后路径相关错误率从34%降至0%。现场记录医疗AI公司的合规代码审查为某医疗AI公司生成患者生存分析图其代码需通过HIPAA合规审查。初始AI代码用plt.savefig(survival.png)被安全团队否决未指定绝对路径可能泄露数据。按习惯四重写# 工程化交付契约HIPAA合规 # input_check: assert patient_id in df.columns, Missing patient_id column # error_handling: try/except FileNotFoundError for data load, ValueError for plot failure # path_handling: output_path Path(/app/output/charts) / fsurvival_{timestamp}.png # type_hints: def plot_survival(df: pd.DataFrame, output_path: Path) - None: # docstring: # Generate Kaplan-Meier survival curve. # Args: # df: DataFrame with time, event, group columns # output_path: Path object for saving PNG # Raises: # ValueError: If time/event columns contain invalid values # AI生成的代码不仅用Path还自动添加output_path.parent.mkdir(exist_okTrue)确保目录存在并在except ValueError中调用logging.error()。更关键的是它把timestamp定义为datetime.now().strftime(%Y%m%d_%H%M%S)满足审计追踪要求。避坑技巧环境隔离的隐式声明当代码需在Docker容器或Airflow中运行别写“要兼容Linux”。我的做法是声明环境约束env_constraints: python_version3.9, packages[pandas1.4,matplotlib3.5]。AI会据此生成requirements.txt片段和版本检查代码import sys if sys.version_info (3, 9): raise RuntimeError(Python 3.9 required)这比口头说明有效得多——它让AI成为你的DevOps协作者。3.5 习惯五错误防御预声明——为AI装上“数据洁癖”滤镜原理为什么“处理缺失值”必须指定方法而非只说“要处理”数据脏是常态但AI的默认行为是“优雅降级”遇到NaN就跳过遇到inf就报错遇到类别不匹配就静默失败。这在探索阶段OK但在交付阶段致命。某物流客户的数据中delivery_time列有0.3%的-1值表示未知AI默认用df.dropna()直接删掉导致分析样本偏差。真正的防御不是事后补救而是事前契约告诉AI“遇到X值必须用Y方法处理并记录Z日志”。这相当于给AI装上数据洁癖滤镜让它从第一行代码就带着防御意识。实操要点错误防御三维声明法数据质量阈值nan_threshold: revenue5% → raise alert, region0% → fill with Unknown异常值处理协议outlier_protocol: revenue → IQR method (whis1.5), cap at Q1-1.5*IQR/Q31.5*IQR类型转换容错type_coercion: date → pd.to_datetime(errorscoerce), then drop NaT; revenue → pd.to_numeric(errorscoerce)关键细节errorscoerce是核心。它让AI生成pd.to_datetime(df[date], errorscoerce)而非pd.to_datetime(df[date])避免因单个坏数据导致整个列转换失败。我统计过声明type_coercion后数据预处理代码一次通过率从42%升至94%。现场记录社交媒体舆情分析的灾难预警分析某品牌微博舆情原始数据中sentiment_score列混有字符串N/A、浮点数0.82、整数-1。初始提示“画情感分布直方图”导致AI用df[sentiment_score].hist()直接报错。按习惯五声明# 错误防御预声明 # nan_threshold: sentiment_score1% → alert, text0% → fill with Empty # outlier_protocol: sentiment_score → clip between -1 and 1 (domain knowledge) # type_coercion: sentiment_score → pd.to_numeric(errorscoerce), then clip(-1,1) # quality_log: print(fCleaned {len(df_orig)-len(df)} rows with invalid sentiment)AI生成的代码不仅用pd.to_numeric(..., errorscoerce)还自动添加df df.clip(lower-1, upper1)并在最后打印清理日志。更棒的是它根据clip(-1,1)推断出需用plt.xlim(-1,1)设置x轴范围让直方图真正反映业务语义。避坑技巧领域知识驱动的防御协议不同行业对“异常”的定义不同。金融数据中revenue0是合法退款但电商数据中revenue0是错误。我的方案是嵌入领域规则字典domain_rules: {finance:{revenue_min:-sys.maxsize}, ecommerce:{revenue_min:0}}。AI会据此生成条件化校验if domain ecommerce: invalid_revenue df[df[revenue] 0] if len(invalid_revenue) 0: logging.warning(f{len(invalid_revenue)} rows with negative revenue) df df[df[revenue] 0]这比通用规则有效得多——它让AI理解业务逻辑而非仅做数学运算。4. 实操全流程演示从原始数据到交付图表的端到端复现4.1 场景设定某SaaS公司用户留存分析项目让我们用一个真实项目贯穿所有习惯。某SaaS公司提供CRM工具需向CEO汇报季度用户留存率。原始数据是CSV文件q3_retention.csv包含字段user_id(str),signup_date(str),active_days(int),plan_type(str: Free,Pro,Enterprise),churn_date(str, 可为空)。业务目标生成一张双Y轴图表左侧显示各计划类型月留存率折线图右侧显示对应用户数柱状图要求符合公司VI规范且能处理churn_date为空未流失的情况。4.2 完整提示词构建五大习惯的协同作战现在我把五大习惯融合成一个生产级提示词。注意观察各习惯如何无缝衔接# 数据结构认知习惯一 # shape: (14287, 5) | memory: 2.1 MB | dtypes: user_id(object), signup_date(object), active_days(int64), plan_type(category), churn_date(object) # sample: user_id[U1001,U1002], signup_date[2023-07-01,2023-07-02], active_days[12,8], plan_type[Pro,Free], churn_date[2023-08-15,None] # nan: churn_date(3241), active_days(0) | outliers: active_days(365: 17 rows) # 视觉语法显式编码习惯二 # chart_type: matplotlib dual-axis plot | left_y: retention_rate(float), right_y: cohort_size(int) # color_map: plan_type→[#0056b3,#6c757d,#28a745] # Free, Pro, Enterprise # layout: titleQ3 2023 User Retention by Plan, left_ylabelRetention Rate (%), right_ylabelCohort Size, legend_locupper right # 设计规范硬性注入习惯三 # brand_colors: primary#0056b3, secondary#6c757d, accent#28a745 # usage_rules: line_colorprimary, bar_colorsecondary20, title_colorprimary, grid_colorsecondary33 # font_system: familyInter, title_size18, label_size14, tick_size12 # 工程化交付契约习惯四 # input_check: assert user_id in df.columns and signup_date in df.columns, Missing required columns # error_handling: try/except around datetime conversion and plotting, log errors with traceback # path_handling: output_path Path(./output) / fretention_q3_{datetime.now().strftime(%Y%m%d)}.png # type_hints: def plot_retention(df: pd.DataFrame, output_path: Path) - None: # 错误防御预声明习惯五 # nan_threshold: churn_date20% → alert, plan_type0% → fill with Unknown # outlier_protocol: active_days → clip(0,365) # max 1 year # type_coercion: signup_date → pd.to_datetime(errorscoerce), churn_date → pd.to_datetime(errorscoerce) # quality_log: print(fProcessed {len(df)} users, {df[churn_date].isna().sum()} active users) # 业务逻辑约束额外强化 # retention_calc: For each cohort (signup month), calculate % of users active at month-end (active_days30) # cohort_grouping: Group by signup_date.month and plan_type4.3 AI生成代码深度解析每一行背后的习惯逻辑AI返回的代码长达127行我们聚焦关键段落看五大习惯如何落地def plot_retention(df: pd.DataFrame, output_path: Path) - None: Generate Q3 2023 retention chart with dual Y-axis. Args: df: DataFrame with user_id, signup_date, active_days, plan_type, churn_date output_path: Path object for saving PNG Raises: ValueError: If signup_date or active_days contain invalid values # 习惯四输入校验 assert user_id in df.columns and signup_date in df.columns, Missing required columns # 习惯五类型转换容错 df[signup_date] pd.to_datetime(df[signup_date], errorscoerce) df[churn_date] pd.to_datetime(df[churn_date], errorscoerce) # 习惯五异常值处理 df[active_days] df[active_days].clip(0, 365) # 习惯一数据质量日志 print(fProcessed {len(df)} users, {df[churn_date].isna().sum()} active users) # 业务逻辑计算留存率习惯五的延伸 df[cohort_month] df[signup_date].dt.to_period(M) # ...详细计算逻辑此处省略... # 习惯二三视觉语法与设计规范融合 fig, ax1 plt.subplots(figsize(12, 6)) ax2 ax1.twinx() # 左Y轴留存率折线图用primary色 for i, (plan, group) in enumerate(retention_df.groupby(plan_type)): ax1.plot(group[month], group[retention_rate], color#0056b3 if planFree else #6c757d if planPro else #28a745, linewidth2.0, labelf{plan} Retention) # 右Y轴用户数柱状图用secondary色透明度 bars ax2.bar(cohort_sizes[cohort_month].

相关新闻