この記事は ACCESS Advent Calendar 2018 の5日目の記事です。
はじめに
Jupyter NotebookでpandasのDataFrameを表示する際に、CSSでスタイルを設定して色付けすることができます。
本記事では、公式ドキュメント にない例として、MultiIndex構造のDataFrameに特定の条件で色を付けて表示する方法を紹介します。
データの準備
これから色付けしていくベースとなるMultiIndex構造のDataFrameは、pandasのテスト用データセットtips.csv
をクロス集計した表を使います。
import pandas as pd
tips = pd.read_csv('https://raw.github.com/pandas-dev/pandas/master/pandas/tests/data/tips.csv')
cross = pd.crosstab([tips.sex, tips.smoker], [tips.day, tips.time])
cross

以降では、このクロス集計表を元に特定の条件で色付けする例を紹介します。
特定の行全体
def highlight(df):
# 返り値は、スタイルで埋めたDataFrameです。引数のDataFrameと構造は同じなので`df`をコピーします。
styles = df.copy()
# DataFrame全体をスタイル無しで初期化します。
styles.loc[:,:] = ''
# `loc[(行の1層目, 行の2層目), (列の1層目, 列の2層目)]`の形式で、スタイルを設定する位置を選択します。
styles.loc[('Male', 'No'), :] = 'background-color: lightgreen'
return styles
# `apply`の第一引数の関数`highlight`でスタイルを設定します。
# `axis=None`にすることで、適用元のDataFrame全体が引数として渡されます。
cross.style.apply(highlight, axis=None)

特定の列全体
def highlight(df):
styles = df.copy()
styles.loc[:,:] = ''
styles.loc[:, ('Sat', 'Dinner')] = 'background-color: lightskyblue'
return styles
cross.style.apply(highlight, axis=None)

特定の要素(行と列を指定)
def highlight(df):
styles = df.copy()
styles.loc[:,:] = ''
styles.loc[('Male', 'No'), ('Sat', 'Dinner')] = 'background-color: orange'
return styles
cross.style.apply(highlight, axis=None)

各行の中の最大値
def highlight(s):
# `is_max`は、最大の要素が`True`のSeriesです。
# 例えば1行目は`[False, False, False, False, False, True]`になります。
is_max = s >= s.max()
styles = is_max.map(lambda x: 'background-color: mediumslateblue; color: white' if x else '')
return styles
# `axis=columns`にすることで、列方向に各行に`highlight`を適用します。
# 各行はSeries型です。
cross.style.apply(highlight, axis='columns')

各列の中の最大値
# `axis=index`にすることで、行方向に各列に`highlight`を適用します。
# 各列はSeries型です。
cross.style.apply(highlight, axis='index')

行同士で比較
行('Female', 'No')
と行('Female', 'Yes')
を比較して、大きい方を色付けする例です。
def highlight(s):
colors = [None] * len(s)
if s[('Female', 'No')] > s[('Female', 'Yes')]:
# s.index.get_locで行番号を取得してスタイルを設定します。
loc = s.index.get_loc(('Female', 'No'))
colors[loc] = 'yellow'
elif s[('Female', 'No')] < s[('Female', 'Yes')]:
loc = s.index.get_loc(('Female', 'Yes'))
colors[loc] = 'tomato'
return ['' if c is None else 'background-color: {}'.format(c) for c in colors]
cross.style.apply(highlight, axis='index')

明日は @shotasakamoto さんです。お楽しみに!