跳转至

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