Pandas データフレームの見た目をカスタマイズする方法 (ケース別)

🍪この記事の内容:
  • pandas.DataFrame を Jupyter Notebook 上で表示する (その他 HTML 化する) とき、セルの背景色や文字色や数値フォーマットなどをカスタマイズする。具体的に、
    • 表全体 / 指定の列全体 / 指定の行全体にスタイルを適用する (Styler.set_table_styles, set_properties, format)。
    • そのセルの値のみに応じたスタイルを適用する (Styler.map)。
    • その行 (列) の値の配列に応じてその行 (列) にスタイルを適用する (Styler.apply)。
      • 例. 行ごとの 1 位と 2 位を目立たせる。
pandas.DataFrame を Jupyter Notebook 上で表示する (その他 HTML 化する) とき、値が 1 位のセルを目立たせるなど見た目をカスタマイズしたいことがある。このようなときは、.style でスタイルシートを操作できるオブジェクト (pandas.io.formats.style.Styler) を取得し、これをカスタマイズして表示すると便利である。以下、ケース別の使用例を示す。

参考文献

  1. pandas.io.formats.style.Styler — pandas 2.3.2 documentation, , 2025年9月26日参照.
  2. 詳細度 - CSS: カスケーディングスタイルシート | MDN, , 2025年10月2日参照.

[参考] そのまま表示する

Jupyter Notebook 上でデータフレームを表示すると以下のように右揃え・偶数行グレーになる (スタイルは大まかに真似たもので、Jupyter Notebook のバージョンにもよると思われる)。df.to_html() を別の HTML に埋め込む場合はそちらでの規定のテーブルスタイルによる。

from IPython.display import HTML, display
import pandas as pd

df = pd.DataFrame({
    'Task Name': ['Task 1', 'Task 2', 'Task 3', 'Task 4'],
    'Model A Loss': [0.1, 0.3, 0.1, 0.1],
    'Model B Loss': [0.2, 0.1, 0.3, 0.1],
    'Model C Loss': [0.3, 0.2, 0.1, 0.1],
})

display(HTML(df.to_html()))
Task Name Model A Loss Model B Loss Model C Loss
0 Task 1 0.1 0.2 0.3
1 Task 2 0.3 0.1 0.2
2 Task 3 0.1 0.3 0.1
3 Task 4 0.1 0.1 0.1

表全体 / 指定の列全体 / 指定の行全体にスタイルを適用する (Styler.set_table_styles, set_properties, format)

セルの値によらないスタイルの適用は set_table_styles を利用すると可読性がよいと思われる。列に関しては set_properties を利用すると列名で指定できる。数値列のフォーマットは format で可能である。
  • set_table_styles は表全体や i 行目や j 列目のスタイルを指定できる。列名やインデックス名でも指定できるが (その場合は引数を dict 型にする)、表全体に適用したいスタイルがあるときは併用できない (set_table_styles を重ねて適用すると最後のスタイルしか適用されない)。
  • set_table_styles の selector には th, td, tr といった HTML 要素を指定できる他、.row0 (0行目のすべてのセル), .col0 (ヘッダ含む0列目のすべてのセル), .data (ヘッダでないすべてのセル) などの定義済みクラスも指定できる (実際に生成された HTML 文字列をみるとよい)。
  • set_properties による列へのスタイル適用はヘッダ行には適用されないので注意。

from IPython.display import HTML, display
import pandas as pd

df = pd.DataFrame({
    'Task Name': ['Task 1', 'Task 2', 'Task 3', 'Task 4'],
    'Model A Loss': [0.1, 0.3, 0.1, 0.1],
    'Model B Loss': [0.2, 0.1, 0.3, 0.1],
    'Model C Loss': [0.3, 0.2, 0.1, 0.1],
})

# Task 3 の行を黄色くしたいとき何行目かをとっておく (0始まりで2行目)
i_row_yellow = df.index.get_loc((df[df['Task Name'] == 'Task 3']).index[0])

styled = df.style.set_table_styles([
    # 表全体のスタイル変更 (枠線, ヘッダ行を灰色, その他の行を白色)
    {'selector': 'th,td', 'props': [('border', '1px solid darkgray')]},
    {'selector': 'th', 'props': [('background', 'lightgray'), ('text-align', 'left')]},
    {'selector': 'td', 'props': [('background', 'white')]},
    # Task 3 の行を黄色に
    {'selector': f'.row{i_row_yellow}', 'props': [('background', 'yellow')]},
    # 0 列目を左揃えに (列名からでも可)
    # {'selector': '.col0', 'props': [('text-align', 'left')]},
]).set_properties(  # 指定の列を左揃え
    subset=['Task Name'], **{'text-align': 'left'},
).format({  # 指定の列の数値をフォーマット
    'Model A Loss': '{:.3f}',
    'Model B Loss': '{:.3f}',
    'Model C Loss': '{:.3f}',
}).hide(axis=0)  # インデクス列を隠す

display(HTML(styled.to_html()))
Task Name Model A Loss Model B Loss Model C Loss
Task 1 0.100 0.200 0.300
Task 2 0.300 0.100 0.200
Task 3 0.100 0.300 0.100
Task 4 0.100 0.100 0.100

そのセルの値のみに応じたスタイルを適用する (Styler.map)

セルの値に応じてスタイルを適用したいこともある。そのセルの値のみに応じたスタイルを適用するときは map を使う。「セルの値を受け取ってスタイルを返す関数」を用意し、適用する列範囲・行範囲と共に渡せばよい。
  • この典型ケースは「負の数値の文字色を赤にする」である (ドキュメントにある)。
  • subset には適用する列範囲・行範囲を渡すが、全体に適用するなら渡さなくてよい。下記の例では列範囲を渡しているが、例えば列範囲も行範囲も絞るときは以下のようにすればよい。
    • .map(subset=([1, 2], ['Model A Loss', 'Model B Loss']), func=_func)
  • なお、set_table_styles でスタイル適用済みのセルに map で同じ CSS プロパティを適用すると set_table_styles が優先されてしまう (詳細度の関係であり、詳しくは参考文献を参照)。この場合は map で適用するスタイルに !important を付ける必要がある。
    • 例えば下記の例では map で文字色を指定しているが、これは set_table_styles と競合していない。もし競合していたら 'color: blue !important;' とする必要がある。

from IPython.display import HTML, display
import pandas as pd

def _func(v):  # セルの値を受け取ってスタイルを返す関数
    if v < 0.15:
        return 'color: blue;'
    if v > 0.25:
        return 'color: red;'
    return ''

df = pd.DataFrame({
    'Task Name': ['Task 1', 'Task 2', 'Task 3', 'Task 4'],
    'Model A Loss': [0.1, 0.3, 0.1, 0.1],
    'Model B Loss': [0.2, 0.1, 0.3, 0.1],
    'Model C Loss': [0.3, 0.2, 0.1, 0.1],
})

styled = df.style \
    .set_table_styles([
        {'selector': 'th,td', 'props': [('border', '1px solid darkgray')]},
        {'selector': 'th', 'props': [('background', 'lightgray'), ('text-align', 'left')]},
        {'selector': 'td', 'props': [('background', 'white')]}]) \
    .format({f'Model {c} Loss': '{:.3f}' for c in 'ABC'}) \
    .set_properties(subset=['Task Name'], **{'text-align': 'left'}) \
    .map(subset=[f'Model {c} Loss' for c in 'ABC'], func=_func)
display(HTML(styled.hide(axis=0).to_html()))
Task Name Model A Loss Model B Loss Model C Loss
Task 1 0.100 0.200 0.300
Task 2 0.300 0.100 0.200
Task 3 0.100 0.300 0.100
Task 4 0.100 0.100 0.100

その行 (列) の値の配列に応じてその行 (列) にスタイルを適用する (Styler.apply)

そのセルの値のみでなく、同じ行 (列) の値にも応じてスタイルを適用したいこともある。このときは apply を使う。例えば行ごとに適用するなら、「行を受け取ってその要素数のスタイルの配列を返す関数」を用意し、適用する列範囲と共に渡せばよい (引数 axis=1 も必要)。列ごとでも同様 (こちらは axis=0)。
  • apply 自体はテーブルを受け取ることもできる (axis=None)。

例. 行ごとの 1 位と 2 位を目立たせる

以下の関数 _highlight(row) はデータフレームの行を受け取り、値の降順の順位の列とし、さらに順位に応じた背景色の列として返す。なお、set_table_styles でもセル背景色を適用しているため !important が必要である (前述)。

from IPython.display import HTML, display
import pandas as pd

conf_color = {  # 何位の背景色を何色にするかの設定 (!important が必要)
    1: 'background: #8dce6f !important;',  # 1位は緑
    2: 'background: #ccf188 !important;',  # 2位は黄緑
}

def _highlight(row):  # 行を受け取ってその要素数のスタイルの配列を返す関数
    rank = row.rank(ascending=True, method='min')
    return rank.map(lambda x: conf_color.get(x, ''))

df = pd.DataFrame({
    'Task Name': ['Task 1', 'Task 2', 'Task 3', 'Task 4'],
    'Model A Loss': [0.1, 0.3, 0.1, 0.1],
    'Model B Loss': [0.2, 0.1, 0.3, 0.1],
    'Model C Loss': [0.3, 0.2, 0.1, 0.1],
})

styled = df.style \
    .set_table_styles([
        {'selector': 'th,td', 'props': [('border', '1px solid darkgray')]},
        {'selector': 'th', 'props': [('background', 'lightgray'), ('text-align', 'left')]},
        {'selector': 'td', 'props': [('background', 'white')]}]) \
    .format({f'Model {c} Loss': '{:.3f}' for c in 'ABC'}) \
    .set_properties(subset=['Task Name'], **{'text-align': 'left'}) \
    .apply(_highlight, axis=1, subset=[f'Model {c} Loss' for c in 'ABC'])
display(HTML(styled.hide(axis=0).to_html()))
Task Name Model A Loss Model B Loss Model C Loss
Task 1 0.100 0.200 0.300
Task 2 0.300 0.100 0.200
Task 3 0.100 0.300 0.100
Task 4 0.100 0.100 0.100