Spec-Zone.ru › pandas 0.25

Стилизация

Новое в версии 0.17.1

Предварительная версия: Эта функция новая и все еще находится в разработке. Мы будем добавлять новые функции и, возможно, вносить несовместимые изменения в будущих выпусках. Мы будем рады получить ваши отзывы.

Этот документ написан в формате Jupyter Notebook и может быть просмотрен или загружен здесь.

Вы можете применить условную стилизацию, визуальную стилизацию DataFrame в зависимости от данных внутри, используя свойство DataFrame.style. Это свойство возвращает объект Styler, который имеет полезные методы для форматирования и отображения DataFrames.

Стилизация выполняется с помощью CSS. Вы пишете «функции стилизации», которые принимают скаляры, DataFrame или Series, и возвращают индексированные DataFrame или Series с парами атрибутов CSS "attribute: value" для значений. Эти функции могут быть поэтапно переданы в Styler, который собирает стили перед отрисовкой.

Создание стилей

Передайте свои функции стилизации в один из следующих методов:

  • Styler.applymap: поэлементно
  • Styler.apply: по столбцам/строкам/таблице

Оба метода принимают функцию (и некоторые другие ключевые аргументы) и применяют вашу функцию к DataFrame определенным образом. Styler.applymap обрабатывает DataFrame поэлементно. Styler.apply передает каждый столбец или строку в ваш DataFrame по одному или всю таблицу целиком, в зависимости от ключевого аргумента axis. Для столбцов используйте axis=0, для строк axis=1, и для всей таблицы целиком axis=None.

Для Styler.applymap ваша функция должна принимать скаляр и возвращать строку с парой атрибут-значение CSS.

Для Styler.apply ваша функция должна принимать Series или DataFrame (в зависимости от параметра axis) и возвращать Series или DataFrame с одинаковой формой, где каждое значение — это строка с парой атрибут-значение CSS.

Давайте рассмотрим несколько примеров.

[2]:
import pandas as pd
import numpy as np

np.random.seed(24)
df = pd.DataFrame({'A': np.linspace(1, 10, 10)})
df = pd.concat([df, pd.DataFrame(np.random.randn(10, 4), columns=list('BCDE'))],
               axis=1)
df.iloc[0, 2] = np.nan

Вот пример рендеринга DataFrame, без стилей:

[3]:
df.style
[3]:
A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072

Примечание: Атрибут DataFrame.style — свойство, которое возвращает объект Styler. У Styler определён метод _repr_html_, поэтому они отображаются автоматически. Если вам нужно получить исходный HTML для дальнейшей обработки или записи в файл, вызовите метод .render(), который возвращает строку.

Вышеприведённый вывод очень похож на стандартное HTML-представление DataFrame. Но мы сделали некоторую работу за кулисами, чтобы прикрепить CSS-классы к каждой ячейке. Их можно просмотреть, вызвав метод .render.

[4]:
df.style.highlight_null().render().split('\n')[:10]
[4]:
['<style  type="text/css" >',
 '    #T_acfc12d6_a988_11e9_a75e_31802e421a9brow0_col2 {',
 '            background-color:  red;',
 '        }</style><table id="T_acfc12d6_a988_11e9_a75e_31802e421a9b" ><thead>    <tr>        <th class="blank level0" ></th>        <th class="col_heading level0 col0" >A</th>        <th class="col_heading level0 col1" >B</th>        <th class="col_heading level0 col2" >C</th>        <th class="col_heading level0 col3" >D</th>        <th class="col_heading level0 col4" >E</th>    </tr></thead><tbody>',
 '                <tr>',
 '                        <th id="T_acfc12d6_a988_11e9_a75e_31802e421a9blevel0_row0" class="row_heading level0 row0" >0</th>',
 '                        <td id="T_acfc12d6_a988_11e9_a75e_31802e421a9brow0_col0" class="data row0 col0" >1</td>',
 '                        <td id="T_acfc12d6_a988_11e9_a75e_31802e421a9brow0_col1" class="data row0 col1" >1.32921</td>',
 '                        <td id="T_acfc12d6_a988_11e9_a75e_31802e421a9brow0_col2" class="data row0 col2" >nan</td>',
 '                        <td id="T_acfc12d6_a988_11e9_a75e_31802e421a9brow0_col3" class="data row0 col3" >-0.31628</td>']

row0_col2 — идентификатор этой конкретной ячейки. Мы также добавили уникальный UUID к каждому идентификатору строки/столбца для каждого DataFrame, чтобы стили одного не конфликтовали со стилями другого в одном и том же блокноте или на одной странице (вы можете установить uuid, чтобы связать стили двух DataFrames).

При написании функций стилей вы сами заботитесь о создании пар атрибутов/значений CSS, которые вам нужны. Pandas сопоставляет их с CSS-классами, идентифицирующими каждую ячейку.

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

[5]:
def color_negative_red(val):
    """
    Takes a scalar and returns a string with
    the css property `'color: red'` for negative
    strings, black otherwise.
    """
    color = 'red' if val < 0 else 'black'
    return 'color: %s' % color

В этом случае стиль ячейки зависит только от её значения. Это означает, что мы должны использовать метод Styler.applymap, который работает поэлементно.

[6]:
s = df.style.applymap(color_negative_red)
s
[6]:
A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072

Обратите внимание на сходство со стандартным df.applymap, который работает с элементами DataFrames. Мы хотим, чтобы вы могли повторно использовать свои существующие знания о взаимодействии с DataFrames.

Также обратите внимание, что наша функция возвращает строку, содержащую атрибут CSS и его значение, разделенные двоеточием, как и в теге <style>. Это будет общей темой.

Наконец, формы ввода совпали. Styler.applymap вызывает функцию для каждого скалярного ввода, и функция возвращает скалярный вывод.

Теперь предположим, что вы хотите выделить максимальное значение в каждом столбце. Мы больше не можем использовать .applymap , так как оно работало с элементами. Вместо этого мы обратимся к .apply, которое работает по столбцам (или по строкам с использованием ключевого слова axis). Позже мы увидим, что что-то вроде highlight_max уже определено в Styler, поэтому вам не нужно будет это писать самостоятельно.

[7]:
def highlight_max(s):
    '''
    highlight the maximum in a Series yellow.
    '''
    is_max = s == s.max()
    return ['background-color: yellow' if v else '' for v in is_max]
[8]:
df.style.apply(highlight_max)
[8]:
A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072

В этом случае входные данные представляют собой Series, по одному столбцу за раз. Обратите внимание, что выходная форма highlight_max соответствует входной форме, массиву с len(s) элементами.

Мы рекомендуем использовать цепочки методов для поэтапного построения стиля, прежде чем окончательно отобразить его в конце цепочки.

[9]:
df.style.\
    applymap(color_negative_red).\
    apply(highlight_max)
[9]:
A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072

Выше мы использовали Styler.apply для передачи каждого столбца по одному.

Совет по отладке: если у вас возникли проблемы с написанием функции стиля, попробуйте просто передать её в DataFrame.apply. Внутри Styler.apply используется DataFrame.apply, поэтому результат должен быть одинаковым.

А что, если вы хотите выделить только максимальное значение во всей таблице? Используйте .apply(function, axis=None) для указания того, что ваша функция хочет всю таблицу, а не один столбец или строку за раз. Попробуем это дальше.

Мы перепишем наш highlight-max для обработки либо Series (из .apply(axis=0 or 1)) или DataFrame (из .apply(axis=None)). Мы также добавим возможность регулировать цвет, чтобы продемонстрировать, что .apply, и .applymap передают ключевые аргументы.

[10]:
def highlight_max(data, color='yellow'):
    '''
    highlight the maximum in a Series or DataFrame
    '''
    attr = 'background-color: {}'.format(color)
    if data.ndim == 1:  # Series from .apply(axis=0) or axis=1
        is_max = data == data.max()
        return [attr if v else '' for v in is_max]
    else:  # from .apply(axis=None)
        is_max = data == data.max().max()
        return pd.DataFrame(np.where(is_max, attr, ''),
                            index=data.index, columns=data.columns)

При использовании Styler.apply(func, axis=None), функция должна возвращать DataFrame с теми же индексами и метками столбцов.

[11]:
df.style.apply(highlight_max, color='darkorange', axis=None)
[11]:
A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072

Резюме стилей построения

Функции стилей должны возвращать строки с одним или несколькими CSS attribute: value разделенными точкой с запятой. Используйте

  • Styler.applymap(func) для стилей на уровне элементов
  • Styler.apply(func, axis=0) для стилей на уровне столбцов
  • Styler.apply(func, axis=1) для стилей на уровне строк
  • Styler.apply(func, axis=None) для стилей на уровне таблицы

И, что очень важно, входные и выходные формы func должны совпадать. Если x - это вход, то func(x).shape == x.shape.

Более тонкий контроль: срезы

И Styler.apply, и Styler.applymap принимают subset ключевое слово. Это позволяет применять стили к определённым строкам или столбцам, без необходимости кодирования этой логики в вашу style функцию.

Значение, переданное subset, ведет себя аналогично срезу DataFrame.

  • Скаляр обрабатывается как метка столбца
  • Список (или ряд или массив NumPy)
  • Кортеж обрабатывается как (row_indexer, column_indexer)

Рекомендуется использовать pd.IndexSlice для построения кортежа в последнем случае.

[12]:
df.style.apply(highlight_max, subset=['B', 'C', 'D'])
[12]:
A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072

Для среза строк и столбцов подойдёт любой допустимый индексатор для .loc.

[13]:
df.style.applymap(color_negative_red,
                  subset=pd.IndexSlice[2:5, ['B', 'D']])
[13]:
A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072

В настоящее время поддерживается только выборка по меткам, а не по позициям.

Если ваша функция стиля использует ключевое слово subset или axis, рассмотрите возможность обернуть вашу функцию в functools.partial, частично исключив это ключевое слово.

my_func2 = functools.partial(my_func, subset=42)

Более тонкий контроль: отображение значений

Мы различаем значение отображения и значение фактического значения в Styler. Чтобы контролировать значение отображения, текст печатается в каждой ячейке, используйте Styler.format. Ячейки могут быть отформатированы в соответствии со строкой формата спецификации или вызываемым объектом, который принимает одно значение и возвращает строку.

[14]:
df.style.format("{:.2%}")
[14]:
A B C D E
0 100.00% 132.92% nan% -31.63% -99.08%
1 200.00% -107.08% -143.87% 56.44% 29.57%
2 300.00% -162.64% 21.96% 67.88% 188.93%
3 400.00% 96.15% 10.40% -48.12% 85.02%
4 500.00% 145.34% 105.77% 16.56% 51.50%
5 600.00% -133.69% 56.29% 139.29% -6.33%
6 700.00% 12.17% 120.76% -0.20% 162.78%
7 800.00% 35.45% 103.75% -38.57% 51.98%
8 900.00% 168.66% -132.60% 142.90% -208.94%
9 1000.00% -12.98% 63.15% -58.65% 29.07%

Используйте словарь для форматирования конкретных столбцов.

[15]:
df.style.format({'B': "{:0<4.0f}", 'D': '{:+.2f}'})
[15]:
A B C D E
0 1 1000 nan -0.32 -0.99081
1 2 -100 -1.43871 +0.56 0.295722
2 3 -200 0.219565 +0.68 1.88927
3 4 1000 0.104011 -0.48 0.850229
4 5 1000 1.05774 +0.17 0.515018
5 6 -100 0.562861 +1.39 -0.063328
6 7 0000 1.2076 -0.00 1.6278
7 8 0000 1.03753 -0.39 0.519818
8 9 2000 -1.32596 +1.43 -2.08935
9 10 -000 0.631523 -0.59 0.29072

Или передайте вызываемый объект (или словарь вызываемых объектов) для более гибкого управления.

[16]:
df.style.format({"B": lambda x: "±{:.2f}".format(abs(x))})
[16]:
A B C D E
0 1 ±1.33 nan -0.31628 -0.99081
1 2 ±1.07 -1.43871 0.564417 0.295722
2 3 ±1.63 0.219565 0.678805 1.88927
3 4 ±0.96 0.104011 -0.481165 0.850229
4 5 ±1.45 1.05774 0.165562 0.515018
5 6 ±1.34 0.562861 1.39285 -0.063328
6 7 ±0.12 1.2076 -0.00204021 1.6278
7 8 ±0.35 1.03753 -0.385684 0.519818
8 9 ±1.69 -1.32596 1.42898 -2.08935
9 10 ±0.13 0.631523 -0.586538 0.29072

Встроенные стили

Наконец, мы ожидаем, что определённые функции стилизации будут достаточно распространёнными, и включили несколько «встроенных» в Styler, так что вам не придётся писать их самим.

[17]:
df.style.highlight_null(null_color='red')
[17]:
A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072

Вы можете создать «карты теплоты» с помощью метода background_gradient. Для этого потребуются matplotlib, и мы будем использовать Seaborn для получения красивой цветовой карты.

[18]:
import seaborn as sns

cm = sns.light_palette("green", as_cmap=True)

s = df.style.background_gradient(cmap=cm)
s
/opt/conda/envs/pandas/lib/python3.7/site-packages/matplotlib/colors.py:479: RuntimeWarning: invalid value encountered in less
  xa[xa < 0] = -1
[18]:
A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072

Styler.background_gradient принимает именованные аргументы low и high. Грубо говоря, они расширяют диапазон ваших данных на low и high процентов, чтобы при преобразовании цветов не использовался весь диапазон цветовой карты. Это полезно, чтобы текст оставался читаемым.

[19]:
# Uses the full color range
df.loc[:4].style.background_gradient(cmap='viridis')
[19]:
A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
[20]:
# Compress the color range
(df.loc[:4]
    .style
    .background_gradient(cmap='viridis', low=.5, high=0)
    .highlight_null('red'))
[20]:
A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018

Также есть .highlight_min и .highlight_max.

[21]:
df.style.highlight_max(axis=0)
[21]:
A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072

Используйте Styler.set_properties в случаях, когда стиль не зависит от значений.

[22]:
df.style.set_properties(**{'background-color': 'black',
                           'color': 'lawngreen',
                           'border-color': 'white'})
[22]:
A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072

Диаграммы столбцов

Вы можете включить “диаграммы столбцов” в вашу таблицу DataFrame.

[23]:
df.style.bar(subset=['A', 'B'], color='#d65f5f')
[23]:
A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072

В версии 0.20.0 появилась возможность дальнейшей настройки столбчатой диаграммы: теперь вы можете выровнять df.style.bar по нулю или по середине значения (в дополнение к уже существующему способу выравнивания минимального значения слева ячейки), и вы можете передать список [color_negative, color_positive].

Вот как вы можете изменить вышеизложенное с помощью нового align='mid' параметра:

[24]:
df.style.bar(subset=['A', 'B'], align='mid', color=['#d65f5f', '#5fba7d'])
[24]:
A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072

Следующий пример призван продемонстрировать поведение новых опций выравнивания:

[25]:
import pandas as pd
from IPython.display import HTML

# Test series
test1 = pd.Series([-100,-60,-30,-20], name='All Negative')
test2 = pd.Series([10,20,50,100], name='All Positive')
test3 = pd.Series([-10,-5,0,90], name='Both Pos and Neg')

head = """
<table>
    <thead>
        <th>Align</th>
        <th>All Negative</th>
        <th>All Positive</th>
        <th>Both Neg and Pos</th>
    </thead>
    </tbody>

"""

aligns = ['left','zero','mid']
for align in aligns:
    row = "<tr><th>{}</th>".format(align)
    for serie in [test1,test2,test3]:
        s = serie.copy()
        s.name=''
        row += "<td>{}</td>".format(s.to_frame().style.bar(align=align,
                                                           color=['#d65f5f', '#5fba7d'],
                                                           width=100).render()) #testn['width']
    row += '</tr>'
    head += row

head+= """
</tbody>
</table>"""


HTML(head)
[25]:
Выравнивание Все отрицательные Все положительные Отрицательные и положительные
left
0 -100
1 -60
2 -30
3 -20
0 10
1 20
2 50
3 100
0 -10
1 -5
2 0
3 90
zero
0 -100
1 -60
2 -30
3 -20
0 10
1 20
2 50
3 100
0 -10
1 -5
2 0
3 90
mid
0 -100
1 -60
2 -30
3 -20
0 10
1 20
2 50
3 100
0 -10
1 -5
2 0
3 90

Применение стилей

Предположим, у вас есть привлекательный стиль для DataFrame, и теперь вы хотите применить тот же стиль к другому DataFrame. Экспортируйте стиль с помощью df1.style.export, и импортируйте его во второй DataFrame с помощью df1.style.set

[26]:
df2 = -df
style1 = df.style.applymap(color_negative_red)
style1
[26]:
A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072
[27]:
style2 = df2.style
style2.use(style1.export())
style2
[27]:
A B C D E
0 -1 -1.32921 nan 0.31628 0.99081
1 -2 1.07082 1.43871 -0.564417 -0.295722
2 -3 1.6264 -0.219565 -0.678805 -1.88927
3 -4 -0.961538 -0.104011 0.481165 -0.850229
4 -5 -1.45342 -1.05774 -0.165562 -0.515018
5 -6 1.33694 -0.562861 -1.39285 0.063328
6 -7 -0.121668 -1.2076 0.00204021 -1.6278
7 -8 -0.354493 -1.03753 0.385684 -0.519818
8 -9 -1.68658 1.32596 -1.42898 2.08935
9 -10 0.12982 -0.631523 0.586538 -0.29072

Обратите внимание, что вы можете совместно использовать стили, даже если они зависят от данных. Стили переоцениваются в новом DataFrame, на который они use.

Другие варианты

Вы ознакомились с несколькими методами для стилизации данных. Styler также предлагает несколько других вариантов стилей, которые не зависят от данных.

  • precision
  • captions
  • стили для всего столбца
  • скрытие индекса или столбцов

Каждый из них можно указать двумя способами:

  • Ключевой аргумент для Styler.__init__
  • Вызов одного из методов .set_ или .hide_, например, .set_caption или .hide_columns

Лучший способ зависит от контекста. Используйте конструктор Styler при создании многих стилизованных DataFrame, которые должны иметь общие свойства. Для интерактивного использования методы .set_ и .hide_ более удобны.

Точность

Вы можете контролировать точность чисел с плавающей точкой, используя стандартный параметр pandas display.precision.

[28]:
with pd.option_context('display.precision', 2):
    html = (df.style
              .applymap(color_negative_red)
              .apply(highlight_max))
html
[28]:
A B C D E
0 1 1.3 nan -0.32 -0.99
1 2 -1.1 -1.4 0.56 0.3
2 3 -1.6 0.22 0.68 1.9
3 4 0.96 0.1 -0.48 0.85
4 5 1.5 1.1 0.17 0.52
5 6 -1.3 0.56 1.4 -0.063
6 7 0.12 1.2 -0.002 1.6
7 8 0.35 1 -0.39 0.52
8 9 1.7 -1.3 1.4 -2.1
9 10 -0.13 0.63 -0.59 0.29

Или через set_precision метод.

[29]:
df.style\
  .applymap(color_negative_red)\
  .apply(highlight_max)\
  .set_precision(2)
[29]:
A B C D E
0 1 1.3 nan -0.32 -0.99
1 2 -1.1 -1.4 0.56 0.3
2 3 -1.6 0.22 0.68 1.9
3 4 0.96 0.1 -0.48 0.85
4 5 1.5 1.1 0.17 0.52
5 6 -1.3 0.56 1.4 -0.063
6 7 0.12 1.2 -0.002 1.6
7 8 0.35 1 -0.39 0.52
8 9 1.7 -1.3 1.4 -2.1
9 10 -0.13 0.63 -0.59 0.29

Установка точности влияет только на выводимое число; полные значения с высокой точностью всегда передаются в ваши функции стилей. Вы всегда можете использовать df.round(2).style, если предпочитаете округлять с самого начала.

Подписи

Регулярные подписи к таблицам могут быть добавлены несколькими способами.

[30]:
df.style.set_caption('Colormaps, with a caption.')\
    .background_gradient(cmap=cm)
[30]:
Цветовые карты, с подписью.
A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072

Стили таблиц

Следующий вариант — «стили таблиц». Это стили, которые применяются к таблице в целом, но не обращают внимания на данные. Некоторые стили, включая псевдоселекторы, такие как :hover, могут использоваться только таким образом.

[31]:
from IPython.display import HTML

def hover(hover_color="#ffff99"):
    return dict(selector="tr:hover",
                props=[("background-color", "%s" % hover_color)])

styles = [
    hover(),
    dict(selector="th", props=[("font-size", "150%"),
                               ("text-align", "center")]),
    dict(selector="caption", props=[("caption-side", "bottom")])
]
html = (df.style.set_table_styles(styles)
          .set_caption("Hover to highlight."))
html
[31]:
Наведение для выделения.
A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072

table_styles должен быть списком словарей. Каждый словарь должен иметь ключи selector и props. Значение для selector должно быть допустимым селектором CSS. Помните, что все стили уже прикреплены к id, уникальному для каждого Styler. Этот селектор дополняет id. Значение для props должно быть списком кортежей ('attribute', 'value').

table_styles чрезвычайно гибкие, но их не так приятно набирать вручную. Мы надеемся собрать некоторые полезные либо в pandas, либо, что предпочтительнее, в новом пакете, который базируется на этих инструментах.

Скрытие индекса или столбцов

Индекс можно скрыть при отрисовке, вызвав Styler.hide_index. Столбцы можно скрыть при отрисовке, вызвав Styler.hide_columns и передав имя столбца или срез столбцов.

[32]:
df.style.hide_index()
[32]:
A B C D E
1 1.32921 nan -0.31628 -0.99081
2 -1.07082 -1.43871 0.564417 0.295722
3 -1.6264 0.219565 0.678805 1.88927
4 0.961538 0.104011 -0.481165 0.850229
5 1.45342 1.05774 0.165562 0.515018
6 -1.33694 0.562861 1.39285 -0.063328
7 0.121668 1.2076 -0.00204021 1.6278
8 0.354493 1.03753 -0.385684 0.519818
9 1.68658 -1.32596 1.42898 -2.08935
10 -0.12982 0.631523 -0.586538 0.29072
[33]:
df.style.hide_columns(['C','D'])
[33]:
A B E
0 1 1.32921 -0.99081
1 2 -1.07082 0.295722
2 3 -1.6264 1.88927
3 4 0.961538 0.850229
4 5 1.45342 0.515018
5 6 -1.33694 -0.063328
6 7 0.121668 1.6278
7 8 0.354493 0.519818
8 9 1.68658 -2.08935
9 10 -0.12982 0.29072

CSS классы

К ячейкам прикреплены определённые CSS классы.

  • Имена индексов и столбцов включают index_name и level<k>, где k — его уровень в многоуровневом индексе
  • Ячейки с метками индексов включают
    • row_heading
    • row<n>, где n — числовая позиция строки
    • level<k>, где k — уровень в многоуровневом индексе
  • Ячейки с метками столбцов включают
    • col_heading
    • col<n>, где n — числовая позиция столбца
    • level<k>, где k — уровень в многоуровневом индексе
  • Ячейки без данных включают blank
  • Ячейки с данными включают data

Ограничения

  • Только для DataFrame (use Series.to_frame().style)
  • Индекс и столбцы должны быть уникальными
  • Нет большого представления, производительность невысока; предназначено для сводных DataFrame
  • Можно стилизовать только значения, а не индекс или столбцы
  • Можно только применять стили, нельзя вставлять новые HTML сущности

Некоторые из этих ограничений будут устранены в будущем.

Термины

  • Функция стиля: функция, передаваемая в Styler.apply или Styler.applymap и возвращающая значения, такие как 'css attribute: value'
  • Встроенные функции стиля: функции стиля, являющиеся методами в Styler
  • Стиль таблицы: словарь с двумя ключами selector и props. selector — CSS селектор, к которому будет применено props. props — список (attribute, value) кортежей. Список стилей таблицы передаётся в Styler.

Интересные примеры

Здесь представлены несколько интересных примеров.

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

[34]:
from IPython.html import widgets
@widgets.interact
def f(h_neg=(0, 359, 1), h_pos=(0, 359), s=(0., 99.9), l=(0., 99.9)):
    return df.style.background_gradient(
        cmap=sns.palettes.diverging_palette(h_neg=h_neg, h_pos=h_pos, s=s, l=l,
                                            as_cmap=True)
    )
A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072
[35]:
def magnify():
    return [dict(selector="th",
                 props=[("font-size", "4pt")]),
            dict(selector="td",
                 props=[('padding', "0em 0em")]),
            dict(selector="th:hover",
                 props=[("font-size", "12pt")]),
            dict(selector="tr:hover td:hover",
                 props=[('max-width', '200px'),
                        ('font-size', '12pt')])
]
[36]:
np.random.seed(25)
cmap = cmap=sns.diverging_palette(5, 250, as_cmap=True)
bigdf = pd.DataFrame(np.random.randn(20, 25)).cumsum()

bigdf.style.background_gradient(cmap, axis=1)\
    .set_properties(**{'max-width': '80px', 'font-size': '1pt'})\
    .set_caption("Hover to magnify")\
    .set_precision(2)\
    .set_table_styles(magnify())
[36]:
Наведите указатель для увеличения
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
0 0.23 1 -0.84 -0.59 -0.96 -0.22 -0.62 1.8 -2.1 0.87 -0.92 -0.23 2.2 -1.3 0.076 -1.2 1.2 -1 1.1 -0.42 2.3 -2.6 2.8 0.68 -1.6
1 -1.7 1.6 -1.1 -1.1 1 0.0037 -2.5 3.4 -1.7 1.3 -0.52 -0.015 1.5 -1.1 -1.9 -1.1 -0.68 -0.81 0.35 -0.055 1.8 -2.8 2.3 0.78 0.44
2 -0.65 3.2 -1.8 0.52 2.2 -0.37 -3 3.7 -1.9 2.5 0.21 -0.24 -0.1 -0.78 -3 -0.82 -0.21 -0.23 0.86 -0.68 1.4 -4.9 3 1.9 0.61
3 -1.6 3.7 -2.3 0.43 4.2 -0.43 -3.9 4.2 -2.1 1.1 0.12 0.6 -0.89 0.27 -3.7 -2.7 -0.31 -1.6 1.4 -1.8 0.91 -5.8 2.8 2.1 0.28
4 -3.3 4.5 -1.9 -1.7 5.2 -1 -3.8 4.7 -0.72 1.1 -0.18 0.83 -0.22 -1.1 -4.3 -2.9 -0.97 -1.8 1.5 -1.8 2.2 -6.3 3.3 2.5 2.1
5 -0.84 4.2 -1.7 -2 5.3 -0.99 -4.1 3.9 -1.1 -0.94 1.2 0.087 -1.8 -0.11 -4.5 -0.85 -2.1 -1.4 0.8 -1.6 1.5 -6.5 2.8 2.1 3.8
6 -0.74 5.4 -2.1 -1.1 4.2 -1.8 -3.2 3.8 -3.2 -1.2 0.34 0.57 -1.8 0.54 -4.4 -1.8 -4 -2.6 -0.2 -4.7 1.9 -8.5 3.3 2.5 5.8
7 -0.44 4.7 -2.3 -0.21 5.9 -2.6 -1.8 5.5 -4.5 -3.2 -1.7 0.18 0.11 0.036 -6 -0.45 -6.2 -3.9 0.71 -3.9 0.67 -7.3 3 3.4 6.7
8 0.92 5.8 -3.3 -0.65 6 -3.2 -1.8 5.6 -3.5 -1.3 -1.6 0.82 -2.4 -0.4 -6.1 -0.52 -6.6 -3.5 -0.043 -4.6 0.51 -5.8 3.2 2.4 5.1
9 0.38 5.5 -4.5 -0.8 7.1 -2.6 -0.44 5.3 -2 -0.33 -0.8 0.26 -3.4 -0.82 -6.1 -2.6 -8.5 -4.5 0.41 -4.7 1.9 -6.9 2.1 3 5.2
10 2.1 5.8 -3.9 -0.98 7.8 -2.5 -0.59 5.6 -2.2 -0.71 -0.46 1.8 -2.8 0.48 -6 -3.4 -7.8 -5.5 -0.7 -4.6 -0.52 -7.7 1.5 5 5.8
11 1.9 4.5 -2.2 -1.4 5.9 -0.49 0.017 5.8 -1 -0.6 0.49 2 -1.5 1.9 -5.9 -4.5 -8.2 -3.4 -2.2 -4.3 -1.2 -7.9 1.4 5.3 5.8
12 3.2 4.2 -3.1 -2.3 5.9 -2.6 0.33 6.7 -2.8 -0.2 1.9 2.6 -1.5 0.75 -5.3 -4.5 -7.6 -2.9 -2.2 -4.8 -1.1 -9 2.1 6.4 5.6
13 2.3 4.5 -3.9 -2 6.8 -3.3 -2.2 8 -2.6 -0.8 0.71 2.3 -0.16 -0.46 -5.1 -3.8 -7.6 -4 0.33 -3.7 -1 -8.7 2.5 5.9 6.7
14 3.8 4.3 -3.9 -1.6 6.2 -3.2 -1.5 5.6 -2.9 -0.33 -0.97 1.7 3.6 0.29 -4.2 -4.1 -6.7 -4.5 -2.2 -2.4 -1.6 -9.4 3.4 6.1 7.5
15 5.6 5.3 -4 -2.3 5.9 -3.3 -1 5.7 -3.1 -0.33 -1.2 2.2 4.2 1 -3.2 -4.3 -5.7 -4.4 -2.3 -1.4 -1.2 -11 2.6 6.7 5.9
16 4.1 4.3 -2.4 -3.3 6 -2.5 -0.47 5.3 -4.8 1.6 0.23 0.099 5.8 1.8 -3.1 -3.9 -5.5 -3 -2.1 -1.1 -0.56 -13 2.1 6.2 4.9
17 5.6 4.6 -3.5 -3.8 6.6 -2.6 -0.75 6.6 -4.8 3.6 -0.29 0.56 5.8 2 -2.3 -2.3 -5 -3.2 -3.1 -2.4 0.84 -13 3.6 7.4 4.7
18 6 5.8 -2.8 -4.2 7.1 -3.3 -1.2 7.9 -4.9 1.4 -0.63 0.35 7.5 0.87 -1.5 -2.1 -4.2 -2.5 -2.5 -2.9 1.9 -9.7 3.4 7.1 4.4
19 4 6.2 -4.1 -4.1 7.2 -4.1 -1.5 6.5 -5.2 -0.24 0.0072 1.2 6.4 -2 -2.6 -1.7 -5.2 -3.3 -2.9 -1.7 1.6 -11 2.8 7.5 3.9

Экспорт в Excel

Новое в версии 0.20.0

Экспериментальная функция: Эта функция новая и находится в стадии разработки. В будущих версиях мы добавим новые функции и, возможно, внесём изменения, которые могут привести к нарушению работы. Мы с удовольствием рассмотрим ваши отзывы.

Некоторую поддержку экспорта стилизованных DataFrames в таблицы Excel предоставляют движки OpenPyXL или XlsxWriter. Обработанные свойства CSS2.2 включают:

  • background-color
  • border-style, border-width, border-color и их {top, right, bottom, left варианты}
  • color
  • font-family
  • font-style
  • font-weight
  • text-align
  • text-decoration
  • vertical-align
  • white-space: nowrap
  • В настоящее время поддерживаются только именованные цвета CSS2 и шестнадцатеричные цвета в формате #rgb или #rrggbb.
  • Для задания специфичных для Excel свойств стиля также доступны следующие псевдо-свойства CSS:
    • number-format
[37]:
df.style.\
    applymap(color_negative_red).\
    apply(highlight_max).\
    to_excel('styled.xlsx', engine='openpyxl')

Скриншот вывода:

Excel spreadsheet with styled DataFrame

Расширяемость

Ядро pandas — это, и останется, его “высокопроизводительные, простые в использовании структуры данных”. С учётом этого, мы надеемся, что DataFrame.style достигнет двух целей:

  • Предоставить API, приятный для интерактивного использования и “достаточно хороший” для многих задач
  • Создать основу для специализированных библиотек, которые можно будет на ней построить
END_OF_DOCUMENT_MARKER

Если вы создадите на основе этого отличную библиотеку, сообщите нам, и мы добавим на неё ссылку.

Наследование

Если стандартный шаблон не совсем соответствует вашим потребностям, вы можете создать подкласс Styler и расширить или переопределить шаблон. Мы покажем пример расширения стандартного шаблона для вставки пользовательского заголовка перед каждой таблицей.

[38]:
from jinja2 import Environment, ChoiceLoader, FileSystemLoader
from IPython.display import HTML
from pandas.io.formats.style import Styler

Мы будем использовать следующий шаблон:

[39]:
with open("templates/myhtml.tpl") as f:
    print(f.read())
{% extends "html.tpl" %}
{% block table %}
<h1>{{ table_title|default("My Table") }}</h1>
{{ super() }}
{% endblock table %}

Теперь, когда мы создали шаблон, нам нужно настроить подкласс Styler, который знает о нём.

[40]:
class MyStyler(Styler):
    env = Environment(
        loader=ChoiceLoader([
            FileSystemLoader("templates"),  # contains ours
            Styler.loader,  # the default
        ])
    )
    template = env.get_template("myhtml.tpl")

Обратите внимание, что мы включаем оригинальный загрузчик в загрузчик нашей среды. Это потому, что мы расширяем оригинальный шаблон, поэтому среде Jinja необходимо иметь возможность найти его.

Теперь мы можем использовать этот пользовательский стайлер. Это __init__ принимает DataFrame.

[41]:
MyStyler(df)
[41]:

Моя таблица

A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072

Наш пользовательский шаблон принимает ключевое слово table_title. Мы можем указать значение в методе .render.

[42]:
HTML(MyStyler(df).render(table_title="Extending Example"))
[42]:

Расширение Примера

A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072

Для удобства, мы предоставляем метод Styler.from_custom_template, который делает то же самое, что и пользовательский подкласс.

[43]:
EasyStyler = Styler.from_custom_template("templates", "myhtml.tpl")
EasyStyler(df)
[43]:

Моя таблица

A B C D E
0 1 1.32921 nan -0.31628 -0.99081
1 2 -1.07082 -1.43871 0.564417 0.295722
2 3 -1.6264 0.219565 0.678805 1.88927
3 4 0.961538 0.104011 -0.481165 0.850229
4 5 1.45342 1.05774 0.165562 0.515018
5 6 -1.33694 0.562861 1.39285 -0.063328
6 7 0.121668 1.2076 -0.00204021 1.6278
7 8 0.354493 1.03753 -0.385684 0.519818
8 9 1.68658 -1.32596 1.42898 -2.08935
9 10 -0.12982 0.631523 -0.586538 0.29072

Вот структура шаблона:

[44]:
with open("templates/template_structure.html") as f:
    structure = f.read()

HTML(structure)
[44]:
before_style
style
<style type="text/css">
table_styles
before_cellstyle
cellstyle
</style>
before_table
table
<table ...>
caption
thead
before_head_rows
head_tr (loop over headers)
after_head_rows
tbody
before_rows
tr (loop over data rows)
after_rows
</table>
after_table

Подробности шаблона см. в репозитории GitHub.

© 2008–2012, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/0.25.0/user_guide/style.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API