跳转至

AI 辅助数据分析与可视化🔗

关键词: 数据分析, 可视化, Python, pandas, matplotlib, seaborn, 统计分析, 科研绘图
难度: ⭐⭐
预计阅读时间: 35 分钟
最后更新: 2026-04-09


本章导读🔗

数据分析和可视化是科研工作中最耗时的环节之一。从实验数据清洗、统计分析到制作符合期刊要求的图表,每一步都涉及大量的编程和调参工作。AI 工具可以极大地加速这一过程——你只需用自然语言描述"我有什么数据、想做什么分析、想画什么图",AI 就能生成可直接运行的 Python 代码。

本章将系统介绍如何用 AI 辅助完成科研数据分析的全流程:

  1. 数据清洗:用 AI 生成 pandas 代码处理原始数据
  2. 统计分析:让 AI 帮你选择合适的统计方法并解释结果
  3. 科研绑定:生成 matplotlib/seaborn 代码,制作达到期刊发表标准的图表
  4. 实操演示:从原始数据到发表级图表的完整流程

前提假设: 你有基本的 Python 使用经验(能运行脚本、了解 pandas/numpy 基础),但不需要精通数据分析或绑定。如果你还不熟悉 Python 环境配置,请先阅读 VSCode Vibe Coding 教程。


1. AI 辅助数据清洗🔗

数据清洗(Data Cleaning)是数据分析的第一步,也是最枯燥但最重要的一步。原始实验数据往往包含缺失值、异常值、格式不一致等问题。传统做法是手动写大量 pandas 代码来处理——现在你可以让 AI 来生成这些代码。

1.1 用 AI 生成 pandas 处理代码🔗

核心思路: 把你的数据结构和处理需求描述清楚,让 AI 一次性生成完整的清洗流程。

通用数据清洗 Prompt 模板:

我有一个实验数据文件 [文件名],格式为 [CSV/Excel/TSV]。
数据包含以下列:
- [列名1]:[数据类型和含义]
- [列名2]:[数据类型和含义]
- ...

数据存在以下问题:
1. [具体问题,如"第3列有空值"]
2. [具体问题,如"时间格式不统一"]
3. [具体问题,如"可能有异常值"]

请用 Python (pandas) 帮我完成数据清洗:
1. 读入数据
2. 检查数据基本信息(shape、dtypes、缺失值统计)
3. 处理上述问题
4. 输出清洗后的数据并保存

请加上必要的注释,并在每一步后打印处理结果以便我检查。

实例——处理蛋白质活性测定数据:

我有一个酶活性测定的实验数据 enzyme_activity.csv,包含以下列:
- sample_id:样本编号(字符串)
- concentration:底物浓度(μM,浮点数)
- absorbance_450:450nm 吸光度(浮点数)
- temperature:反应温度(℃,应为 25 或 37)
- replicate:重复编号(1, 2, 3)
- date:实验日期(格式不统一,有 "2025-03-01" 和 "03/01/2025" 混用)

数据有约 500 行。已知问题:
1. 约 5% 的 absorbance_450 是空值
2. date 列格式不统一
3. 有几个 absorbance_450 为负值(仪器错误)
4. concentration 列有个别 "#N/A" 文本

请用 pandas 完成数据清洗,并生成一份清洗报告(清洗前后对比)。

AI 生成的代码通常类似:

import pandas as pd
import numpy as np

# ============ 1. 读入数据 ============
df = pd.read_csv('enzyme_activity.csv', na_values=['#N/A', 'N/A', ''])

print("=== 原始数据概况 ===")
print(f"数据量: {df.shape[0]} 行, {df.shape[1]} 列")
print(f"\n数据类型:\n{df.dtypes}")
print(f"\n缺失值统计:\n{df.isnull().sum()}")
print(f"\n数值列描述性统计:\n{df.describe()}")

# ============ 2. 统一日期格式 ============
df['date'] = pd.to_datetime(df['date'], format='mixed')
print(f"\n日期范围: {df['date'].min()} ~ {df['date'].max()}")

# ============ 3. 处理负值(仪器错误) ============
n_negative = (df['absorbance_450'] < 0).sum()
print(f"\n吸光度负值数量: {n_negative}")
df.loc[df['absorbance_450'] < 0, 'absorbance_450'] = np.nan

# ============ 4. 处理缺失值 ============
n_missing_before = df['absorbance_450'].isnull().sum()
# 对每个 (sample_id, concentration) 组,用同组均值填充
df['absorbance_450'] = df.groupby(['sample_id', 'concentration'])[
    'absorbance_450'
].transform(lambda x: x.fillna(x.mean()))
n_missing_after = df['absorbance_450'].isnull().sum()
print(f"\n缺失值处理: {n_missing_before} → {n_missing_after}")

# 仍然为空的行(整组都缺失),直接删除
df = df.dropna(subset=['absorbance_450'])

# ============ 5. 验证 temperature 值 ============
invalid_temp = df[~df['temperature'].isin([25, 37])]
if len(invalid_temp) > 0:
    print(f"\n异常温度值: {invalid_temp['temperature'].unique()}")
    df = df[df['temperature'].isin([25, 37])]

# ============ 6. 保存清洗后数据 ============
df.to_csv('enzyme_activity_cleaned.csv', index=False)
print(f"\n=== 清洗完成 ===")
print(f"最终数据量: {df.shape[0]} 行")

关键技巧: 在 Prompt 中尽可能详细地描述数据结构和已知问题。AI 无法看到你的实际数据文件,所以信息越完整,生成的代码就越准确。

1.2 缺失值处理策略🔗

处理缺失值是数据清洗中最常见的任务。不同场景适合不同的策略:

策略 适用场景 pandas 代码 注意事项
直接删除 缺失比例 <5%,数据量充足 df.dropna() 最简单但会丢失数据
均值/中位数填充 数值型、近似正态分布 df.fillna(df.mean()) 会降低数据方差
前向/后向填充 时间序列数据 df.fillna(method='ffill') 适合连续测量
分组填充 有分组结构的数据 df.groupby('group')['col'].transform(...) 保留组内差异
插值 平滑变化的数值 df.interpolate() 适合趋势性数据
标记为特殊值 缺失本身有含义 df.fillna(-999) 仅在特定模型中适用

让 AI 帮你选择策略的 Prompt:

我的数据集有以下缺失值情况:
- 总共 1000 行数据
- column_A:缺失 3%(数值型,正态分布)
- column_B:缺失 15%(类别型,3 个类别)
- column_C:缺失 30%(数值型,是某些样本没有测量)
- column_D:缺失 1%(数值型,时间序列)

请为每一列推荐最合适的缺失值处理策略,
解释理由,并给出 pandas 代码。

1.3 异常值检测🔗

异常值(Outlier)在实验数据中非常常见,可能是仪器故障、操作失误或真实的极端值。AI 可以帮你快速实现多种检测方法。

异常值检测 Prompt:

请用 Python 对我的数据进行异常值检测,使用以下三种方法:
1. Z-score 方法(阈值 ±3)
2. IQR 方法(1.5倍四分位距)
3. 可视化方法(箱线图 + 直方图)

数据列为 df['absorbance_450'],大约 500 个数值。
请对比三种方法检测到的异常值,并给出处理建议。

AI 通常会生成包含可视化的完整检测代码:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

def detect_outliers(series, name="data"):
    """用三种方法检测异常值"""
    fig, axes = plt.subplots(1, 3, figsize=(15, 4))

    # --- 方法 1: Z-score ---
    z_scores = np.abs((series - series.mean()) / series.std())
    outliers_z = z_scores > 3

    # --- 方法 2: IQR ---
    Q1, Q3 = series.quantile(0.25), series.quantile(0.75)
    IQR = Q3 - Q1
    lower, upper = Q1 - 1.5 * IQR, Q3 + 1.5 * IQR
    outliers_iqr = (series < lower) | (series > upper)

    # --- 可视化 ---
    axes[0].boxplot(series.dropna())
    axes[0].set_title(f'{name} - Boxplot')

    axes[1].hist(series.dropna(), bins=30, edgecolor='black')
    axes[1].axvline(lower, color='r', linestyle='--', label=f'IQR bounds')
    axes[1].axvline(upper, color='r', linestyle='--')
    axes[1].set_title(f'{name} - Histogram + IQR')
    axes[1].legend()

    axes[2].scatter(range(len(series)), series, c=outliers_z, 
                    cmap='coolwarm', alpha=0.5, s=10)
    axes[2].set_title(f'{name} - Z-score Outliers')

    plt.tight_layout()
    plt.savefig(f'outlier_detection_{name}.png', dpi=150, bbox_inches='tight')
    plt.show()

    print(f"Z-score 方法检测到 {outliers_z.sum()} 个异常值")
    print(f"IQR 方法检测到 {outliers_iqr.sum()} 个异常值")

    return outliers_z, outliers_iqr

# 使用
outliers_z, outliers_iqr = detect_outliers(df['absorbance_450'], 'Absorbance')

注意: 异常值不一定要删除!在科研中,异常值可能代表了有意义的生物学现象。AI 可以帮你检测异常值,但是否删除需要你根据科学判断来决定。

1.4 数据类型转换与格式统一🔗

实验数据最常见的格式问题:

请帮我处理以下数据格式问题,用 pandas 代码实现:

1. 时间列:有 "2025-03-01", "03/01/2025", "March 1, 2025" 三种格式
   → 统一为 "YYYY-MM-DD" 格式
2. 浓度列:有 "10 μM", "10μM", "10uM", "0.01 mM" 混用
   → 统一为 μM 数值
3. 基因名:有 "TP53", "tp53", "Tp53" 大小写混乱
   → 统一为全大写
4. 数值列中混入了文本:"<LOD"(低于检测限), "N/A"
   → "<LOD" 替换为检测限的一半值(LOD=0.1),"N/A" 设为空值

2. AI 辅助统计分析🔗

统计分析是论文方法和结果部分的核心。选错统计方法是 reviewer 退稿的常见原因之一。AI 可以帮你根据数据特征选择合适的方法,生成分析代码,并解释结果——但你必须理解统计方法的基本逻辑,不能完全依赖 AI。

2.1 选择合适的统计方法🔗

统计方法选择是初学者最容易犯错的地方。 以下是常见科研场景的统计方法速查表:

研究目的 数据条件 推荐方法 Python 库
比较两组均值 正态分布、方差齐性 独立样本 t 检验 (Independent t-test) scipy.stats.ttest_ind
比较两组均值 非正态分布 Mann-Whitney U 检验 scipy.stats.mannwhitneyu
比较配对数据 正态分布 配对 t 检验 (Paired t-test) scipy.stats.ttest_rel
比较配对数据 非正态分布 Wilcoxon 符号秩检验 scipy.stats.wilcoxon
比较三组及以上均值 正态、方差齐性 单因素方差分析 (One-way ANOVA) scipy.stats.f_oneway
比较三组及以上均值 非正态分布 Kruskal-Wallis 检验 scipy.stats.kruskal
ANOVA 显著后两两比较 — Tukey HSD 事后检验 statsmodels.stats.multicomp
两个连续变量是否相关 线性关系、正态 Pearson 相关 scipy.stats.pearsonr
两个连续变量是否相关 非线性或非正态 Spearman 秩相关 scipy.stats.spearmanr
两个分类变量是否独立 频率数据 卡方检验 (Chi-square) scipy.stats.chi2_contingency
预测连续因变量 线性关系 线性回归 (Linear Regression) sklearn.linear_model / statsmodels
预测二分类因变量 — 逻辑回归 (Logistic Regression) sklearn.linear_model
生存分析 时间-事件数据 Kaplan-Meier / Cox 回归 lifelines
多组间多变量差异 多个因变量 MANOVA / PCA + 聚类 statsmodels / sklearn

让 AI 帮你选择统计方法的 Prompt:

请帮我选择合适的统计分析方法。我的实验设计如下:

实验目的:比较 [具体目的]
实验分组:[列出各组,如"野生型 vs 突变体 vs 突变体+药物处理"]
样本量:每组 [n] 个
测量指标:[具体指标,如"蛋白表达量(连续变量)"]
数据特点:[如"初步观察基本符合正态分布"或"有明显偏态"]

请:
1. 推荐最合适的统计方法及理由
2. 告诉我需要先做哪些前提检验(正态性、方差齐性等)
3. 给出完整的 Python 代码(包含前提检验和主分析)
4. 说明如何正确报告结果(按 APA 格式)

2.2 AI 辅助统计代码生成🔗

完整统计分析流程示例 Prompt:

我有一个蛋白质表达量的实验数据,三组(Control, Treatment_A, Treatment_B),
每组 10 个生物学重复。数据存在 df 中,列为 'group' 和 'expression'。

请用 Python 完成以下分析:
1. 描述性统计(均值、标准差、标准误)
2. 正态性检验(Shapiro-Wilk)
3. 方差齐性检验(Levene's test)
4. 根据检验结果选择合适的方法进行组间比较
5. 如果总体比较显著,做事后多重比较
6. 生成一个包含显著性标注的柱状图
7. 打印规范的统计结果报告

AI 生成的代码通常如下:

import pandas as pd
import numpy as np
from scipy import stats
from statsmodels.stats.multicomp import pairwise_tukeyhsd
import matplotlib.pyplot as plt
import seaborn as sns

# ============ 1. 描述性统计 ============
desc = df.groupby('group')['expression'].agg(['mean', 'std', 'sem', 'count'])
print("=== 描述性统计 ===")
print(desc.round(3))
print()

# ============ 2. 正态性检验 ============
print("=== Shapiro-Wilk 正态性检验 ===")
groups = df['group'].unique()
normal_flag = True
for g in groups:
    data = df[df['group'] == g]['expression']
    stat, p = stats.shapiro(data)
    result = "正态" if p > 0.05 else "非正态"
    print(f"  {g}: W={stat:.4f}, p={p:.4f} → {result}")
    if p <= 0.05:
        normal_flag = False
print()

# ============ 3. 方差齐性检验 ============
group_data = [df[df['group'] == g]['expression'].values for g in groups]
stat, p = stats.levene(*group_data)
equal_var = p > 0.05
print(f"=== Levene 方差齐性检验 ===")
print(f"  F={stat:.4f}, p={p:.4f} → {'方差齐性' if equal_var else '方差不齐'}")
print()

# ============ 4. 组间比较 ============
if normal_flag and equal_var:
    # 满足参数检验条件 → One-way ANOVA
    stat, p = stats.f_oneway(*group_data)
    print(f"=== One-way ANOVA ===")
    print(f"  F={stat:.4f}, p={p:.4f}")
    test_name = "ANOVA"
else:
    # 不满足 → Kruskal-Wallis
    stat, p = stats.kruskal(*group_data)
    print(f"=== Kruskal-Wallis 检验 ===")
    print(f"  H={stat:.4f}, p={p:.4f}")
    test_name = "Kruskal-Wallis"

# ============ 5. 事后多重比较 ============
if p < 0.05:
    print(f"\n=== Tukey HSD 事后比较 ===")
    tukey = pairwise_tukeyhsd(df['expression'], df['group'], alpha=0.05)
    print(tukey)

2.3 让 AI 解释统计结果🔗

拿到统计结果后,可以让 AI 帮你用论文语言描述:

以下是我的统计分析结果:

- 三组数据:Control (mean=1.00, SD=0.15, n=10), Treatment_A (mean=1.45, SD=0.20, n=10), 
  Treatment_B (mean=1.82, SD=0.25, n=10)
- Shapiro-Wilk 检验: 三组均 p>0.05(正态)
- Levene 检验: p=0.23(方差齐性)
- One-way ANOVA: F(2,27)=47.3, p<0.001
- Tukey HSD: Control vs Treatment_A (p=0.001), Control vs Treatment_B (p<0.001), 
  Treatment_A vs Treatment_B (p=0.003)

请用以下格式帮我撰写 Results 部分的统计描述:
1. 英文版本(适合提交论文)
2. 中文版本(适合组会报告)
遵循 APA 统计报告规范。

2.4 常见统计陷阱🔗

以下是 reviewer 最常指出的统计问题,也是 AI 可能忽略的地方:

常见错误 正确做法 说明
不检验正态性就用 t 检验 先做 Shapiro-Wilk 检验 小样本尤其关键
多次 t 检验代替 ANOVA 三组以上先做 ANOVA,再做事后检验 否则 I 类错误膨胀
p 值报告为 "p=0.000" 写 "p<0.001" p 值不会等于 0
只报告 p 值不报告效应量 同时报告效应量(Cohen's d, η²) 越来越多期刊要求
两组数据不独立却用独立 t 检验 配对设计用配对 t 检验 前后对比、左右对比
混淆生物学重复和技术重复 n = 生物学重复数 这是最常见的审稿意见
非正态数据用均值±SD 用中位数±IQR 或展示完整分布 boxplot 比 barplot 信息量大
不做多重比较校正 Bonferroni / Benjamini-Hochberg 校正 多次检验时必须校正

让 AI 帮你检查统计分析的 Prompt:

以下是我论文中 Results 部分的统计分析描述。
请以一个严格的统计学审稿人的角度检查:
1. 统计方法选择是否正确?
2. 前提假设检验是否充分?
3. 结果报告格式是否规范?
4. 是否存在常见的统计错误?
5. 还需要补充哪些分析?

我的实验设计:[简述]
统计描述:[粘贴你的 Results 段落]

2.5 效应量计算🔗

越来越多的期刊要求在报告 p 值的同时报告效应量(Effect Size)。一些常用的效应量指标:

请帮我计算以下统计分析的效应量,用 Python 实现:

1. 两组 t 检验 → Cohen's d
2. One-way ANOVA → η²(eta-squared)和 ω²(omega-squared)
3. 相关分析 → r 本身就是效应量,但请给出 r² 和 95% CI
4. 卡方检验 → Cramér's V

数据在 df 中,分组列为 'group',数值列为 'value'。

同时请告诉我如何解释这些效应量的大小
(小效应/中效应/大效应的阈值)。

常见效应量解读标准:

效应量 小效应 中效应 大效应
Cohen's d 0.2 0.5 0.8
η² 0.01 0.06 0.14
r 0.1 0.3 0.5
Cramér's V 0.1 0.3 0.5

3. AI 辅助科研绑定🔗

科研绘图是 AI 辅助编程中投入产出比最高的场景。传统手动调参一张图可能需要几个小时,而用 AI 只需几分钟就能生成接近发表质量的代码——剩下的只需要微调细节。

3.1 期刊图片格式要求🔗

在让 AI 生成绑定代码之前,必须先了解目标期刊的图片要求。以下是主要期刊的通用规范:

主要期刊图片规格一览🔗

要求 Nature Science Cell 一般期刊
文件格式 TIFF/EPS/PDF EPS/PDF TIFF/EPS/PDF TIFF/PDF/PNG
分辨率(DPI) 300(照片)/600(线图) 300+ 300+ 300+
颜色模式 RGB(线上)/ CMYK(印刷) RGB RGB/CMYK RGB
单栏宽度 89 mm (3.5 in) 85 mm (3.35 in) 85 mm (3.35 in) 80-90 mm
双栏宽度 183 mm (7.2 in) 174 mm (6.85 in) 174 mm (6.85 in) 170-180 mm
最大高度 247 mm (9.7 in) 230 mm (9.1 in) 247 mm (9.7 in) ~240 mm
字体 Helvetica/Arial Helvetica/Arial Arial/Helvetica Arial/Helvetica
最小字号 5 pt 6 pt 6 pt 6 pt
推荐字号 7-8 pt 7-9 pt 7-8 pt 7-10 pt
线宽 0.5-1.5 pt 0.5-1.0 pt 0.5-1.0 pt 0.5-1.5 pt

配色方案推荐🔗

基本原则: 科研图表的配色要同时满足美观、可区分和色盲友好三个标准。

配色方案 适用场景 来源 Python 代码
Paul Tol's Bright 离散分类(≤7 色) colorbrewer ['#4477AA','#EE6677','#228833','#CCBB44','#66CCEE','#AA3377','#BBBBBB']
ColorBrewer Set2 离散分类(≤8 色) seaborn 内置 sns.color_palette('Set2')
Viridis 连续色阶 matplotlib 默认 plt.cm.viridis
RdBu 双向发散(如热图) matplotlib plt.cm.RdBu_r
Nature 常用 仿 Nature 风格 自定义 ['#E64B35','#4DBBD5','#00A087','#3C5488','#F39B7F','#8491B4']
Science 常用 仿 Science 风格 自定义 ['#3B4992','#EE0000','#008B45','#631879','#008280','#BB0021']

色盲友好提示: 避免同时使用 红+绿 的组合。约 8% 的男性有红绿色盲。建议使用 蓝+橙、蓝+红 的搭配,或使用 Viridis/Cividis 色阶。可以用 Color Oracle 软件模拟色盲视觉效果。

3.2 发表级绑定的基础设置🔗

以下是一套"万能"的 matplotlib 全局设置,适用于大多数期刊:

import matplotlib.pyplot as plt
import matplotlib as mpl

# ============ 期刊级图表全局设置 ============
def set_publication_style():
    """设置适合期刊发表的 matplotlib 全局参数"""
    mpl.rcParams.update({
        # --- 字体 ---
        'font.family': 'Arial',       # 大多数期刊要求 Arial 或 Helvetica
        'font.size': 8,                # 基础字号 8pt
        'axes.titlesize': 9,           # 子图标题
        'axes.labelsize': 8,           # 坐标轴标签
        'xtick.labelsize': 7,          # 刻度标签
        'ytick.labelsize': 7,
        'legend.fontsize': 7,          # 图例

        # --- 线条 ---
        'axes.linewidth': 0.8,         # 坐标轴线宽
        'xtick.major.width': 0.8,      # 刻度线宽
        'ytick.major.width': 0.8,
        'xtick.major.size': 3,         # 刻度线长
        'ytick.major.size': 3,
        'lines.linewidth': 1.0,        # 数据线宽
        'lines.markersize': 4,         # 标记大小

        # --- 图像 ---
        'figure.dpi': 150,             # 屏幕显示 DPI
        'savefig.dpi': 300,            # 保存 DPI(照片用 300,线图用 600)
        'savefig.bbox': 'tight',       # 自动裁剪白边
        'savefig.pad_inches': 0.05,    # 留极小的边距

        # --- 其他 ---
        'axes.spines.top': False,      # 隐藏上边框
        'axes.spines.right': False,    # 隐藏右边框(更简洁)
        'legend.frameon': False,       # 图例无边框
        'pdf.fonttype': 42,            # PDF 中嵌入字体(期刊要求)
        'ps.fonttype': 42,
    })

# 在脚本开头调用
set_publication_style()

# Nature 单栏宽度
FIG_WIDTH_SINGLE = 89 / 25.4   # 89mm → inches
# Nature 双栏宽度
FIG_WIDTH_DOUBLE = 183 / 25.4  # 183mm → inches

重要: pdf.fonttype: 42 这个设置非常关键!它确保 PDF 中的字体是 TrueType 而非 Type 3,否则很多期刊的投稿系统会报错。

3.3 常见科研图表类型及 Prompt 模板🔗

柱状图 + 显著性标注(最常用)🔗

请用 Python (matplotlib) 画一张适合 Nature 投稿的柱状图:

数据:三组(Control, Treatment_A, Treatment_B),
每组 mean 和 SEM 已计算好。
显著性:Control vs Treatment_A (p<0.05), 
        Control vs Treatment_B (p<0.001)

要求:
- 单栏宽度(89mm),高度约 60mm
- 每个 bar 上添加散点(jitter)展示原始数据
- 显著性标注用横线 + 星号(* p<0.05, ** p<0.01, *** p<0.001)
- 配色方案:Nature 风格(灰、蓝、红)
- Y 轴标签:Relative Expression
- 无上边框和右边框
- 字体 Arial,8pt
- 保存为 PDF 和 600 DPI 的 TIFF

以下是 AI 通常生成的参考代码(经过微调):

import matplotlib.pyplot as plt
import numpy as np

set_publication_style()  # 使用上面定义的全局设置

# --- 数据 ---
groups = ['Control', 'Treatment A', 'Treatment B']
means = [1.0, 1.45, 1.82]
sems = [0.05, 0.06, 0.08]
# 原始数据点(用于散点叠加)
raw_data = [
    np.random.normal(1.0, 0.15, 10),
    np.random.normal(1.45, 0.20, 10),
    np.random.normal(1.82, 0.25, 10)
]

# --- Nature 配色 ---
colors = ['#BFBFBF', '#4DBBD5', '#E64B35']

# --- 绑定 ---
fig, ax = plt.subplots(figsize=(89/25.4, 60/25.4))  # 单栏宽,适中高度

# 柱状图
bars = ax.bar(range(3), means, yerr=sems, capsize=3,
              color=colors, edgecolor='black', linewidth=0.5,
              width=0.6, error_kw={'linewidth': 0.8})

# 散点叠加
for i, data in enumerate(raw_data):
    jitter = np.random.uniform(-0.15, 0.15, len(data))
    ax.scatter(i + jitter, data, color='black', s=8, alpha=0.6,
               zorder=5, linewidths=0)

# 显著性标注
def add_significance(ax, x1, x2, y, p_text, h=0.03):
    """添加显著性标注横线和星号"""
    ax.plot([x1, x1, x2, x2], [y, y+h, y+h, y], lw=0.8, c='black')
    ax.text((x1+x2)/2, y+h, p_text, ha='center', va='bottom', fontsize=7)

add_significance(ax, 0, 1, 1.7, '*')
add_significance(ax, 0, 2, 1.9, '***')

# --- 格式调整 ---
ax.set_xticks(range(3))
ax.set_xticklabels(groups)
ax.set_ylabel('Relative Expression')
ax.set_ylim(0, 2.2)

# 保存
fig.savefig('barplot.pdf')
fig.savefig('barplot.tiff', dpi=600)
plt.show()

折线图(时间序列 / 剂量-反应)🔗

请画一张展示酶活性随底物浓度变化的 Michaelis-Menten 曲线:

数据:6 个浓度点,每个 3 次重复
X 轴:Substrate Concentration (μM)
Y 轴:Reaction Rate (μM/min)
拟合方程:v = Vmax * [S] / (Km + [S])

要求:
- 散点展示原始数据(带误差棒,SEM)
- 叠加拟合曲线(虚线或实线均可)
- 标注 Vmax 和 Km 值
- 图片大小:Nature 单栏宽度
- 保存为 PDF

热图(Heatmap)🔗

请用 seaborn 画一张基因表达热图:

数据:pandas DataFrame,行是基因名(20 个),列是样本(6 个)
数值是 log2(fold change),范围约 [-3, 3]

要求:
- 使用 RdBu_r 配色(红=上调,蓝=下调)
- 行列都做层次聚类(hierarchical clustering)
- 添加列注释(标注 Control / Treatment)
- colorbar 标签"log₂(Fold Change)"
- 基因名字号 6pt
- 图片大小约 90mm x 120mm
import seaborn as sns
import pandas as pd
import numpy as np

# 使用 seaborn clustermap
g = sns.clustermap(
    df_expression,
    cmap='RdBu_r',
    center=0,
    vmin=-3, vmax=3,
    figsize=(90/25.4, 120/25.4),
    dendrogram_ratio=(0.15, 0.15),
    cbar_kws={'label': r'$\log_2$(Fold Change)', 'shrink': 0.6},
    linewidths=0.5,
    linecolor='white',
    xticklabels=True,
    yticklabels=True,
    col_colors=col_color_map,  # 列注释颜色
    method='ward',
    metric='euclidean'
)

# 调整字体大小
g.ax_heatmap.tick_params(axis='both', labelsize=6)
g.ax_heatmap.set_xlabel('')
g.ax_heatmap.set_ylabel('')

plt.savefig('heatmap.pdf', bbox_inches='tight')

火山图(Volcano Plot)🔗

请画一张差异基因表达分析的火山图:

数据:DataFrame 包含列 'gene', 'log2FC', 'pvalue', 'padj'
阈值:|log2FC| > 1 且 padj < 0.05 为显著

要求:
- X 轴:log₂(Fold Change),Y 轴:-log₁₀(p_adj)
- 显著上调基因标红,显著下调标蓝,不显著标灰
- 标注 top 10 显著基因名(避免文字重叠,用 adjustText 库)
- 添加阈值虚线(横向和纵向)
- 在图中标注显著基因数量
- Nature 单栏宽度
import matplotlib.pyplot as plt
import numpy as np
from adjustText import adjust_text

set_publication_style()

fig, ax = plt.subplots(figsize=(89/25.4, 75/25.4))

# 分类标注
conditions = [
    (df['log2FC'] > 1) & (df['padj'] < 0.05),   # 显著上调
    (df['log2FC'] < -1) & (df['padj'] < 0.05),   # 显著下调
]
colors = ['#E64B35', '#4DBBD5', '#BFBFBF']  # 红、蓝、灰
labels = [f'Up ({conditions[0].sum()})', 
          f'Down ({conditions[1].sum()})', 
          'NS']

# 计算 -log10(padj)
df['neg_log10p'] = -np.log10(df['padj'].clip(lower=1e-300))

# 不显著的灰色点先画(在底层)
ns = ~conditions[0] & ~conditions[1]
ax.scatter(df.loc[ns, 'log2FC'], df.loc[ns, 'neg_log10p'],
           c='#BFBFBF', s=3, alpha=0.5, label=f'NS ({ns.sum()})')

# 显著上调
ax.scatter(df.loc[conditions[0], 'log2FC'], 
           df.loc[conditions[0], 'neg_log10p'],
           c='#E64B35', s=5, alpha=0.7, label=labels[0])

# 显著下调
ax.scatter(df.loc[conditions[1], 'log2FC'], 
           df.loc[conditions[1], 'neg_log10p'],
           c='#4DBBD5', s=5, alpha=0.7, label=labels[1])

# 阈值线
ax.axhline(-np.log10(0.05), ls='--', lw=0.5, c='grey')
ax.axvline(-1, ls='--', lw=0.5, c='grey')
ax.axvline(1, ls='--', lw=0.5, c='grey')

# 标注 top 基因
sig = df[conditions[0] | conditions[1]].nlargest(10, 'neg_log10p')
texts = []
for _, row in sig.iterrows():
    texts.append(ax.text(row['log2FC'], row['neg_log10p'], row['gene'],
                         fontsize=5, style='italic'))
adjust_text(texts, arrowprops=dict(arrowstyle='-', color='grey', lw=0.5))

ax.set_xlabel(r'$\log_2$(Fold Change)')
ax.set_ylabel(r'$-\log_{10}$(adjusted p-value)')
ax.legend(fontsize=6, loc='upper right', markerscale=1.5)

fig.savefig('volcano_plot.pdf')
plt.show()

小提琴图 / 箱线图组合🔗

请画一张展示各组数据分布的小提琴图叠加箱线图:

三组数据,用 seaborn 实现:
- 外层小提琴图展示数据分布形状
- 内层箱线图展示四分位数
- 叠加散点展示原始数据
- 添加显著性标注

Nature 单栏宽度,保存 PDF。
import seaborn as sns
import matplotlib.pyplot as plt

set_publication_style()
fig, ax = plt.subplots(figsize=(89/25.4, 65/25.4))

# 小提琴图
parts = ax.violinplot([data_ctrl, data_trtA, data_trtB], 
                       positions=[0, 1, 2], showextrema=False)
for i, pc in enumerate(parts['bodies']):
    pc.set_facecolor(colors[i])
    pc.set_alpha(0.3)

# 箱线图叠加
bp = ax.boxplot([data_ctrl, data_trtA, data_trtB],
                positions=[0, 1, 2], widths=0.15,
                patch_artist=True, showfliers=False,
                medianprops=dict(color='black', linewidth=1),
                boxprops=dict(facecolor='white', linewidth=0.8))

# 散点叠加
for i, data in enumerate([data_ctrl, data_trtA, data_trtB]):
    jitter = np.random.uniform(-0.05, 0.05, len(data))
    ax.scatter(i + jitter, data, color=colors[i], s=10, alpha=0.6,
               edgecolors='none', zorder=5)

ax.set_xticks([0, 1, 2])
ax.set_xticklabels(['Control', 'Treatment A', 'Treatment B'])
ax.set_ylabel('Expression Level')

fig.savefig('violin_box.pdf')

3.4 多子图布局(Multi-panel Figure)🔗

期刊论文中的 Figure 通常由多个子图组成。这是手动排版最头痛的部分,也是 AI 特别擅长的地方。

多子图布局 Prompt 模板:

请帮我用 matplotlib 创建一个包含 6 个子图的复合图(Figure 1):

布局:
  A  B  C
  D  D  E

其中面板 D 跨两列。

各面板内容:
- A: 柱状图(3 组均值对比 + 误差棒 + 显著性)
- B: 散点图(X vs Y,带线性回归拟合线和 R² 值)
- C: 箱线图(4 组数据分布)
- D: 折线图(时间序列,5 条曲线,带 SEM shade)
- E: 饼图(样本组成比例)

要求:
- 整体大小:Nature 双栏宽度(183mm),高度约 120mm
- 每个面板左上角标注 a, b, c, d, e(小写粗体,10pt)
- 统一配色方案
- 保存为 PDF

用 gridspec 实现不等宽布局的示例:

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

set_publication_style()

fig = plt.figure(figsize=(183/25.4, 120/25.4))

# 创建不等宽网格
gs = gridspec.GridSpec(2, 3, 
                       height_ratios=[1, 1],
                       width_ratios=[1, 1, 1],
                       hspace=0.35, wspace=0.35)

ax_a = fig.add_subplot(gs[0, 0])     # A: 第一行第一列
ax_b = fig.add_subplot(gs[0, 1])     # B: 第一行第二列
ax_c = fig.add_subplot(gs[0, 2])     # C: 第一行第三列
ax_d = fig.add_subplot(gs[1, 0:2])   # D: 第二行跨前两列
ax_e = fig.add_subplot(gs[1, 2])     # E: 第二行第三列

# 面板标签
for ax, label in zip([ax_a, ax_b, ax_c, ax_d, ax_e], 
                      ['a', 'b', 'c', 'd', 'e']):
    ax.text(-0.15, 1.10, label, transform=ax.transAxes,
            fontsize=10, fontweight='bold', va='top')

# [在每个 ax 上绑定对应内容...]

fig.savefig('Figure1.pdf')
fig.savefig('Figure1.tiff', dpi=600)

3.5 AI 辅助图表迭代优化🔗

画图是一个反复迭代的过程。利用 AI 进行迭代修改非常高效:

第一轮:生成基础图

[描述图表需求,如上面的 Prompt]

第二轮:微调

基于上面的代码,请做以下修改:
1. X 轴标签旋转 45 度
2. Y 轴范围改为 0-5
3. 图例位置移到右上角
4. 把 Treatment_A 的颜色从蓝色改为 #00A087
5. 添加一条水平虚线标注 baseline(y=1.0)

第三轮:精修

最终版本,请做以下微调:
1. 增大子图标签的间距
2. 误差棒的端线(cap)太长了,改为 2pt
3. 图例中不需要显示"NS"那一项
4. X 轴和 Y 轴的刻度标签对齐问题修一下

技巧: 在 VS Code + Copilot 中,你可以选中绑定代码,用 Inline Chat(Cmd+I)直接说"把柱状图改成水平的"或"配色改成 Science 风格",Copilot 会直接修改选中的代码。


4. 实操演示:从原始数据到发表级图表🔗

以下是一个完整的端到端工作流演示,展示如何从一份原始实验数据出发,借助 AI 完成数据清洗、统计分析和绑定。

4.1 场景设定🔗

实验背景: 你做了一个蛋白质表达量实验。用 Western Blot 定量了三组样本(Control、Drug_A、Drug_B)的目标蛋白表达水平(归一化到 β-actin),每组 6 个生物学重复。你需要分析组间差异,并制作一张适合投稿 Nature 的柱状图。

4.2 步骤 1:用 AI 生成完整分析脚本🔗

一次性大 Prompt:

请帮我完成一个完整的蛋白质表达量分析和绘图脚本。

## 实验设计
- 三组:Control (n=6), Drug_A (n=6), Drug_B (n=6)
- 测量指标:目标蛋白与 β-actin 的灰度值之比(归一化表达量)
- 数据已整理在 CSV 文件中,列为 group, expression

## 分析要求
1. 描述性统计
2. 正态性检验(Shapiro-Wilk)和方差齐性检验(Levene)
3. 根据检验结果选择合适的组间比较方法
4. 事后两两比较
5. 计算效应量(Cohen's d)

## 绘图要求
画一张 Nature 风格的柱状图:
- 单栏宽度(89mm),高度约 65mm
- 柱子颜色:灰(Control)、蓝(Drug A)、红(Drug B)
- 误差棒为 SEM
- 柱子上叠加散点(原始数据)
- 显著性标注(横线 + 星号)
- Y 轴标签:Relative Protein Expression
- 隐藏上边框和右边框
- 字体 Arial,8pt
- 保存为 PDF 和 TIFF(600 DPI)

## 结果输出
请同时打印一段英文的统计结果描述(适合直接放到论文里)。

请生成完整的 Python 脚本,包含所有必要的 import 和注释。

4.3 步骤 2:AI 生成的完整脚本🔗

以下是根据上述 Prompt,AI 通常会生成的完整脚本(已调试优化):

#!/usr/bin/env python3
"""
蛋白质表达量分析与绘图脚本
分析三组 Western Blot 定量数据,生成 Nature 风格柱状图
"""

import pandas as pd
import numpy as np
from scipy import stats
from statsmodels.stats.multicomp import pairwise_tukeyhsd
import matplotlib.pyplot as plt
import matplotlib as mpl
from itertools import combinations

# ================================================================
# 全局设置
# ================================================================
mpl.rcParams.update({
    'font.family': 'Arial',
    'font.size': 8,
    'axes.labelsize': 8,
    'axes.titlesize': 9,
    'xtick.labelsize': 7,
    'ytick.labelsize': 7,
    'legend.fontsize': 7,
    'axes.linewidth': 0.8,
    'xtick.major.width': 0.8,
    'ytick.major.width': 0.8,
    'axes.spines.top': False,
    'axes.spines.right': False,
    'figure.dpi': 150,
    'savefig.dpi': 300,
    'savefig.bbox': 'tight',
    'pdf.fonttype': 42,
    'ps.fonttype': 42,
})

# Nature 风格配色
COLORS = {
    'Control': '#BFBFBF',
    'Drug_A': '#4DBBD5',
    'Drug_B': '#E64B35'
}

# ================================================================
# 1. 读入数据
# ================================================================
df = pd.read_csv('protein_expression.csv')
print("=" * 50)
print("1. 数据概况")
print("=" * 50)
print(df.head())
print(f"\n样本量: {df.shape[0]}")
print(f"各组样本量:\n{df['group'].value_counts()}")

# ================================================================
# 2. 描述性统计
# ================================================================
print("\n" + "=" * 50)
print("2. 描述性统计")
print("=" * 50)
desc = df.groupby('group')['expression'].agg(
    ['count', 'mean', 'std', 'sem', 'median']
).round(4)
print(desc)

# ================================================================
# 3. 正态性检验
# ================================================================
print("\n" + "=" * 50)
print("3. Shapiro-Wilk 正态性检验")
print("=" * 50)
groups = ['Control', 'Drug_A', 'Drug_B']
group_data = {}
all_normal = True
for g in groups:
    data = df[df['group'] == g]['expression'].values
    group_data[g] = data
    stat, p = stats.shapiro(data)
    normal = "✓ 正态" if p > 0.05 else "✗ 非正态"
    if p <= 0.05:
        all_normal = False
    print(f"  {g}: W={stat:.4f}, p={p:.4f} {normal}")

# ================================================================
# 4. 方差齐性检验
# ================================================================
print("\n" + "=" * 50)
print("4. Levene 方差齐性检验")
print("=" * 50)
stat, p = stats.levene(*group_data.values())
equal_var = p > 0.05
print(f"  F={stat:.4f}, p={p:.4f} {'✓ 方差齐性' if equal_var else '✗ 方差不齐'}")

# ================================================================
# 5. 组间比较
# ================================================================
print("\n" + "=" * 50)
print("5. 组间比较")
print("=" * 50)

if all_normal and equal_var:
    stat, p_overall = stats.f_oneway(*group_data.values())
    print(f"[One-way ANOVA]  F={stat:.4f}, p={p_overall:.6f}")
    test_method = "One-way ANOVA"
else:
    stat, p_overall = stats.kruskal(*group_data.values())
    print(f"[Kruskal-Wallis]  H={stat:.4f}, p={p_overall:.6f}")
    test_method = "Kruskal-Wallis"

# ================================================================
# 6. 事后多重比较
# ================================================================
pairwise_results = {}
if p_overall < 0.05:
    print("\n" + "=" * 50)
    print("6. 事后比较 (Tukey HSD)")
    print("=" * 50)
    tukey = pairwise_tukeyhsd(df['expression'], df['group'], alpha=0.05)
    print(tukey)

    # 提取 p 值
    for i, (g1, g2) in enumerate(combinations(groups, 2)):
        p_val = tukey.pvalues[i]
        pairwise_results[(g1, g2)] = p_val

# ================================================================
# 7. 效应量 (Cohen's d)
# ================================================================
print("\n" + "=" * 50)
print("7. 效应量 (Cohen's d)")
print("=" * 50)

def cohens_d(a, b):
    na, nb = len(a), len(b)
    pooled_std = np.sqrt(((na-1)*np.std(a,ddof=1)**2 + 
                           (nb-1)*np.std(b,ddof=1)**2) / (na+nb-2))
    return (np.mean(a) - np.mean(b)) / pooled_std

for g1, g2 in combinations(groups, 2):
    d = cohens_d(group_data[g1], group_data[g2])
    size = "小" if abs(d) < 0.5 else ("中" if abs(d) < 0.8 else "大")
    print(f"  {g1} vs {g2}: d={d:.3f} ({size}效应)")

# ================================================================
# 8. 绑定
# ================================================================
print("\n" + "=" * 50)
print("8. 生成图表")
print("=" * 50)

fig, ax = plt.subplots(figsize=(89/25.4, 65/25.4))

# 柱状图
means = [group_data[g].mean() for g in groups]
sems = [stats.sem(group_data[g]) for g in groups]
x_pos = np.arange(len(groups))

bars = ax.bar(x_pos, means, yerr=sems, capsize=3,
              color=[COLORS[g] for g in groups],
              edgecolor='black', linewidth=0.5,
              width=0.6, error_kw={'linewidth': 0.8})

# 散点叠加
for i, g in enumerate(groups):
    data = group_data[g]
    jitter = np.random.default_rng(42).uniform(-0.12, 0.12, len(data))
    ax.scatter(i + jitter, data, color='black', s=12, alpha=0.6,
               zorder=5, linewidths=0.3, edgecolors='white')

# 显著性标注
def add_sig(ax, x1, x2, y, p_val):
    if p_val >= 0.05:
        return  # 不显著不标注
    text = '***' if p_val < 0.001 else ('**' if p_val < 0.01 else '*')
    h = 0.02 * (ax.get_ylim()[1] - ax.get_ylim()[0])
    ax.plot([x1, x1, x2, x2], [y, y+h, y+h, y], lw=0.8, c='black')
    ax.text((x1+x2)/2, y+h, text, ha='center', va='bottom', fontsize=7)

# 动态计算标注位置
y_max = max(max(group_data[g]) for g in groups)
sig_y = y_max * 1.05
for (g1, g2), p_val in pairwise_results.items():
    i1, i2 = groups.index(g1), groups.index(g2)
    add_sig(ax, i1, i2, sig_y, p_val)
    sig_y += y_max * 0.12  # 逐级升高避免重叠

# 格式
ax.set_xticks(x_pos)
ax.set_xticklabels([g.replace('_', ' ') for g in groups])
ax.set_ylabel('Relative Protein Expression')
ax.set_ylim(0, sig_y + y_max * 0.1)

# 保存
fig.savefig('Figure_protein_expression.pdf')
fig.savefig('Figure_protein_expression.tiff', dpi=600)
print("  ✓ 已保存 Figure_protein_expression.pdf")
print("  ✓ 已保存 Figure_protein_expression.tiff (600 DPI)")
plt.show()

# ================================================================
# 9. 论文用统计描述
# ================================================================
print("\n" + "=" * 50)
print("9. 论文用统计描述(英文)")
print("=" * 50)

mean_ctrl = desc.loc['Control', 'mean']
sem_ctrl = desc.loc['Control', 'sem']
mean_a = desc.loc['Drug_A', 'mean']
sem_a = desc.loc['Drug_A', 'sem']
mean_b = desc.loc['Drug_B', 'mean']
sem_b = desc.loc['Drug_B', 'sem']

p_str = "p<0.001" if p_overall < 0.001 else f"p={p_overall:.3f}"

print(f"""
Protein expression levels were compared across three groups using 
{test_method}. Drug A treatment significantly increased target protein 
expression compared to the control group ({mean_a:.2f} ± {sem_a:.2f} vs 
{mean_ctrl:.2f} ± {sem_ctrl:.2f}, mean ± SEM; Tukey HSD, 
p={'<0.001' if pairwise_results.get(('Control','Drug_A'),1)<0.001 
   else f"={pairwise_results.get(('Control','Drug_A'),1):.3f}"}). 
Drug B treatment showed an even greater increase ({mean_b:.2f} ± {sem_b:.2f}; 
p={'<0.001' if pairwise_results.get(('Control','Drug_B'),1)<0.001 
   else f"={pairwise_results.get(('Control','Drug_B'),1):.3f}"} vs control). 
Overall {test_method}: {p_str}. All values are presented as mean ± SEM 
(n=6 per group).
""")

4.4 步骤 3:运行与迭代🔗

  1. 首次运行:执行脚本,检查统计结果和图表
  2. 迭代修改:对 AI 说具体需要调整的地方
请帮我修改上面的代码:
1. Y 轴从 0 开始,不要有间断
2. 把 "Drug_A" 显示为 "Drug A (10 μM)"
3. 在图的右下角加上 "n=6" 的标注
4. 误差棒改用 SD 而不是 SEM(审稿人要求的)

4.5 常用 "一句话" 快速绘图 Prompt🔗

当你已经有了数据,只需要快速出图时:

需求 推荐 Prompt
快速查看数据分布 "画 df['column'] 的直方图和 QQ 图并排展示"
两变量关系 "画 X vs Y 的散点图,加线性回归拟合线,标注 R² 和 p 值"
多组对比 "画 4 组数据的箱线图 + 散点,加显著性标注"
时间序列 "画 3 条曲线的折线图,带 SEM 的阴影区域"
相关性矩阵 "画 10 个变量的 Pearson 相关性热图,只显示下三角"
PCA "对 df 做 PCA,画前两个主成分的散点图,按 group 着色"
生存曲线 "画 Kaplan-Meier 生存曲线,两组对比,标注 log-rank p 值"

5. 进阶应用🔗

5.1 用 AI 批量处理多个数据文件🔗

我有一个文件夹 ./data/,里面有 30 个 CSV 文件,命名格式为
"sample_001.csv" ~ "sample_030.csv"。
每个文件包含两列:time (s) 和 fluorescence。

请写一个脚本:
1. 批量读取所有文件
2. 对每个文件的数据做基线校正(减去前 10 个点的均值)
3. 计算所有样本的平均曲线和 SEM
4. 画一张图:淡色线条显示所有单独曲线 + 深色粗线显示平均值 + 阴影显示 SEM
5. 保存汇总数据为一个 CSV

5.2 用 AI 复现论文图表🔗

看到论文里一张好看的图想复现?直接让 AI 帮你:

请帮我用 Python 复现以下类型的图表(类似 Nature 2024 年某文章风格):

图表描述:
- 左侧是一个分组柱状图(grouped bar chart),4 个分组,每组 3 个 bar
- 右侧是一个散点图,X 轴是预测值,Y 轴是实验值,带对角线
- 两个面板并排排列,标注 a 和 b
- 颜色风格类似 Nature

请给出完整代码,用随机模拟数据演示。

5.3 数据分析自动化报告🔗

请帮我写一个 Python 脚本,自动生成数据分析报告:
1. 读取数据文件
2. 自动检测数据类型(数值型/类别型)
3. 对数值列生成描述性统计和分布图
4. 对类别列生成频率表和柱状图
5. 计算所有数值列之间的相关矩阵
6. 将所有图表和统计结果汇总到一个 PDF 报告中

使用 matplotlib 的 PdfPages 功能将多页图表保存到一个 PDF。

本章小结🔗

  • ✅ 数据清洗:用详细的 Prompt 描述数据结构和问题,让 AI 生成 pandas 清洗代码;务必检查清洗前后的数据完整性
  • ✅ 统计分析:根据实验设计和数据特征选择正确的统计方法;先做前提检验(正态性、方差齐性),再做主分析;报告效应量
  • ✅ 科研绘图:使用标准化的全局设置(字体、字号、DPI、线宽);掌握 Nature/Science 的图片规格要求;善用 AI 迭代优化图表
  • ✅ 效率工作流:一个大 Prompt 搞定"数据读取→统计分析→绘图→论文描述"全流程
  • ✅ 核心原则:AI 生成的代码和统计结果必须经过你的检查和理解,不能盲目信任。特别是统计方法的选择,需要你具备基本的统计学知识

记住: AI 是你的编程加速器,不是你的大脑替代品。让 AI 写代码,你来做科学判断。


延伸阅读🔗


参考文献🔗


  1. VanderPlas, J. (2016). Python Data Science Handbook. O'Reilly Media. ↩

  2. Rougier, N.P., et al. (2014). "Ten Simple Rules for Better Figures." PLOS Computational Biology, 10(9), e1003833. ↩

  3. Weissgerber, T.L., et al. (2015). "Beyond Bar and Line Graphs: Time for a New Data Presentation Paradigm." PLOS Biology, 13(4), e1002128. ↩

  4. Nature. "Formatting Guide: Figures." https://www.nature.com/nature/for-authors/formatting-guide ↩

  5. Science. "Instructions for Authors." https://www.science.org/content/page/instructions-authors ↩