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 и обеспечивают предсказуемые отступы без изменений вашей логики агрегации.