2025, Dec 09 00:02

Предсказуемые отступы в pandas в Jupyter: неразрывные пробелы и CSS через Styler

Как сделать видимые отступы в pandas в Jupyter Notebook: быстрый метод с неразрывными пробелами и гибкий — через CSS и Styler с text-indent для подытогов и итогов

Отрисовать подитоги и общий итог с визуальной иерархией в Jupyter Notebook кажется простым, пока не пытаешься сделать отступы в названиях обычными пробелами. Pandas выводит значения, но видимый отступ пропадает в сгенерированном HTML. Ниже — краткий разбор проблемы и два рабочих способа добиться стабильных отступов в выводе ноутбука.

Настройка и где всё идет не так

Данные приходят из базы в виде Series с MultiIndex. Задача — посчитать подитоги и общий итог, вывести плоскую таблицу и визуально сместить подписи, чтобы передать структуру.

from pandas import MultiIndex, DataFrame, Series

mi_idx = MultiIndex.from_tuples(
    [
        ('total', 'subtotal', 'a'),
        ('total', 'subtotal', 'b'),
        ('total', 'subtotal', 'c'),
        ('total', 'subtotal', 'd'),
        ('total', 'foo', 'foo'),
        ('total', 'bar', 'bar')
    ],
    names=['module_1', 'module_2', 'module_3']
)

raw_musd = [
    106.564338488296,
    60.5686589737704,
    311.695156173571,
    -90.3794796906721,
    29.6147863260859,
    -49.0048344046974
]

base_df = Series(raw_musd, index=mi_idx, name='musd').to_frame()

scheme_df = DataFrame(
    [
        ("a", "module_3", 4),
        ("b", "module_3", 4),
        ("c", "module_3", 4),
        ("d", "module_3", 4),
        ("subtotal", "module_2", 2),
        ("foo", "module_3", 2),
        ("bar", "module_3", 2),
        ("total", "module_1", 0)
    ],
    columns=["name", "sum_over", "indent"]
)

out_dict = {
    f"{' ' * depth}{label}": base_df.query(f"{lvl} == '{label}'")["musd"].sum()
    for label, lvl, depth in scheme_df.values
}

pretty_df = DataFrame(out_dict, index=[0]).T.reset_index().set_axis(['', 'musd'], axis=1)

pretty_df.style.format(precision=1)

С вычислениями всё в порядке, и строки в первом столбце действительно содержат пробелы. Но видимых отступов в итоговой таблице нет — даже если выровнять столбец по левому краю.

Что на самом деле происходит

При HTML-рендеринге стилизованного DataFrame в ноутбуке заполнение обычными пробелами не воспринимается как отступ. Простого выравнивания слева тоже недостаточно. Чтобы отступ был заметен в выводе, нужны либо неразрывные пробелы, либо отступы через CSS.

Два простых решения

Если нужен быстрый результат, замените начальные пробелы на неразрывные и выровняйте первый столбец по левому краю. Если помимо отступов важны курсив, жирное начертание и границы при сохранении разделения контента и оформления, используйте CSS через pandas Styler.

Быстрое решение с неразрывными пробелами:

import pandas as pd

table_quick = pd.DataFrame(
    list({
        4 * " " + "a": 106.6,
        4 * " " + "b": 60.6,
        2 * " " + "subtotal": 388.4,
        0 * " " + "total": 369.1
    }.items()),
    columns=["", "musd"]
)

view_quick = table_quick.style.set_properties(subset=[""], **{"text-align": "left"})
view_quick

Подход с упором на CSS: отступы, курсив, жирное начертание и границы:

import pandas as pd

vals_css = {"a": 106.6, "b": 60.6, "subtotal": 388.4, "total": 369.1}
table_css = pd.DataFrame(list(vals_css.items()), columns=["", "musd"])

sty_css = table_css.style.format(precision=1).set_properties(
    subset=[""], **{"text-align": "left"}
)

sty_css = sty_css.set_properties(
    subset=pd.IndexSlice[0:1, :],
    **{"text-indent": "2em", "font-style": "italic"}
)

sty_css = sty_css.set_properties(
    subset=pd.IndexSlice[2, :],
    **{"text-indent": "1em"}
)

sty_css = sty_css.set_properties(
    subset=pd.IndexSlice[3, :],
    **{
        "border-top": "1px solid black",
        "border-bottom": "3px double black",
        "font-weight": "bold"
    }
)

sty_css

Если хотите скрыть столбец индекса, добавьте sty_css.hide(axis="index") перед выводом.

Почему это полезно знать

Дата-сайентисты и инженеры много сообщают результаты прямо в ноутбуках. Небольшие штрихи — отступы для иерархии, выделение итогов, аккуратные границы — ускоряют чтение и снижают риск ошибок. Понимание, что HTML‑представление в ноутбуке не учитывает начальные пробелы так, как вы рассчитываете, экономит время и наводит на надёжные, воспроизводимые приёмы.

Итоги

Для быстрых правок замените начальные пробелы на   и задайте выравнивание слева. Для большего контроля и более чистого разделения содержимого и оформления опирайтесь на CSS через pandas Styler: text-indent — для иерархии, font-style и font-weight — для акцентов, границы — для визуального отделения. Оба подхода хорошо работают в Jupyter и обеспечивают предсказуемые отступы без изменений вашей логики агрегации.