2025, Oct 22 09:01
Фигуры Seaborn под \textwidth в LaTeX: масштаб, шрифты и экспорт PDF
Как исправить тесные графики Seaborn при ширине \textwidth: согласуем размеры шрифтов, включим рендеринг LaTeX, сохраним в PDF и уменьшим маркеры при экспорте.
Когда вы подгоняете фигуры seaborn под ширину LaTeX \textwidth, а графики всё равно выглядят тесными, это раздражает. Частый признак — фигура визуально меньше ожидаемого: маркеры и подписи сбиты, и «правильно» она смотрится только после удвоения размера. Но задача в другом: сохранить целевую ширину и одновременно выдержать размеры шрифтов в соответствии с основным текстом статьи.
Как воспроизвести проблему
Ниже приведена настройка для ширины макета 7 дюймов: три панели расположены в ряд. График сочетает stripplot с boxplot, делит ось Y и поворачивает подписи делений по оси X — типичный случай для публикационной графики. Обратите внимание: на размеры подписей есть ссылки, но сами значения заранее явно не заданы.
import matplotlib.pyplot as plt
import seaborn as sns
page_width = 7.00925
metrics = ['test_1', 'test_2', 'test_3']
panel_titles = ['Test 1', 'Test 2', 'Test 3']
canvas, axarr = plt.subplots(1, 3, figsize=(page_width, 3), sharey=True)
for idx, (target_col, ttl) in enumerate(zip(metrics, panel_titles)):
    pane = axarr[idx]
    # Точечный график (stripplot)
    sns.stripplot(
        x='diagnosis_grouped', y=target_col, data=plot_frame, ax=pane,
        palette='viridis', alpha=0.7, jitter=True
    )
    # Ящиковый график (boxplot)
    sns.boxplot(
        x='group', y=target_col, data=plot_frame, ax=pane, showfliers=False,
        width=0.3,
        boxprops=dict(facecolor='none', edgecolor='black'),
        medianprops=dict(color='black'),
        whiskerprops=dict(color='black'),
        capprops=dict(color='black')
    )
    pane.set_title(ttl, fontsize=label_size)
    pane.set_xlabel('Group', fontsize=label_size)
    if idx == 0:
        pane.set_ylabel('Score', fontsize=label_size)
    else:
        pane.set_ylabel('')
    pane.tick_params(axis='x', rotation=20, labelsize=label_size)
    pane.tick_params(axis='y', labelsize=label_size)
plt.tight_layout(rect=[0, 0, 1, 0.95])
plt.savefig('/path/figure_1.eps', dpi=800)
Что на самом деле происходит
Визуальное восприятие определяют два независимых фактора. Во‑первых, увеличение figsize заставляет те же данные занимать больше горизонтального пространства — точки и коробки естественным образом «рассасываются» и кажутся менее плотными. Во‑вторых, если размеры шрифтов прямо не согласованы с документом, подписи осей и делений могут выглядеть чужеродно, даже когда ширина фигуры выбрана верно. Как только шрифты заданы осознанно, та же ширина воспринимается на полосе так, как задумано. А рендеринг текста через LaTeX дополнительно гарантирует, что подписи и формулы совпадают с типографикой документа.
Решение
Сохраните целевую ширину. Задайте конкретный размер подписей под ваш документ (например, 11). Включите рендеринг текста через LaTeX для единообразной типографики. Сохраняйте в вектор, например PDF, с плотной рамкой (tight bounding box), чтобы удержать резкость и контролировать поля. Если точки всё ещё выглядят тесно, уменьшите размер маркеров в sns.stripplot через параметр size.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib import rcParams
# Использовать LaTeX для отрисовки всего текста
rcParams.update({"text.usetex": True})
# Настроить размеры шрифта под рукопись
rcParams.update({
    "font.size": 11,
    "axes.labelsize": 11,
    "axes.titlesize": 11,
    "xtick.labelsize": 11,
    "ytick.labelsize": 11,
    "legend.fontsize": 11,
})
label_size = 11
page_width_in = 7.00925
panel_height_in = 3
frame_metrics = ['test_1', 'test_2', 'test_3']
frame_titles = ['Test 1', 'Test 2', 'Test 3']
board, panels = plt.subplots(1, 3, figsize=(page_width_in, panel_height_in), sharey=True)
for j, (col_name, title_txt) in enumerate(zip(frame_metrics, frame_titles)):
    slot = panels[j]
    sns.stripplot(
        x='diagnosis_grouped', y=col_name, data=plot_frame, ax=slot,
        palette='viridis', alpha=0.7, jitter=True
        # size=4  # при необходимости уменьшите размер точек, если выглядит тесно
    )
    sns.boxplot(
        x='group', y=col_name, data=plot_frame, ax=slot, showfliers=False,
        width=0.3,
        boxprops=dict(facecolor='none', edgecolor='black'),
        medianprops=dict(color='black'),
        whiskerprops=dict(color='black'),
        capprops=dict(color='black')
    )
    slot.set_title(title_txt, fontsize=label_size)
    slot.set_xlabel('Group', fontsize=label_size)
    if j == 0:
        slot.set_ylabel('Score', fontsize=label_size)
    else:
        slot.set_ylabel('')
    slot.tick_params(axis='x', rotation=20, labelsize=label_size)
    slot.tick_params(axis='y', labelsize=label_size)
plt.tight_layout(rect=[0, 0, 1, 0.95])
plt.savefig('/path/figure_1.pdf', bbox_inches='tight')
plt.show()
Почему это важно для публикационных процессов
Иллюстрации для LaTeX должны быть выровнены по размеру и типографике, чтобы органично вписываться в макет. Программное согласование шрифтов убирает догадки, а рендеринг текста через LaTeX предотвращает рассинхрон между подписями на графике и текстом статьи. Экспорт в PDF сохраняет векторную чёткость для печати и электронных версий и гарантирует, что масштабирование в финальной рукописи не ухудшит читаемость.
Итоги
Задайте холст под целевую ширину, затем явно установите размеры шрифтов, чтобы подписи совпадали с рукописью. Используйте рендеринг текста LaTeX для единой типографики и сохраняйте в PDF с плотной рамкой для аккуратного результата. Если при корректной ширине визуальная плотность всё ещё высока, уменьшайте размер точек в stripplot, а не раздувайте фигуру. Так вы сохраните соответствие \textwidth без ущерба для читаемости и компоновки.
Статья основана на вопросе с StackOverflow от RBG и ответе Thomas Beck.