はじめに
前回、Jupyter上にダッシュボードを作成できるPanelというライブラリを紹介しました。
⇒ ”エコ”にダッシュボードをつくろう ~Panelライブラリの紹介~
学習コストが少ないというのが特徴のライブラリですが、各ウィジェットのオプション設定は色々調べないと分からないようなものも多数ありました。後学のため整理しておきます。
今回は主にユーザーのアクションを受け取る入力系ウィジェットについてまとめます。
2021/12/11追記 出力系のウィジェットまとめました!
ダッシュボード作成ライブラリPanelのウィジェット紹介 ~出力系編~
共通
Panelを扱うには、pipなどでpanelをインストールした上で下記のおまじないを冒頭で行なう必要があります。
# ! pip install panel
import panel as pn
# おまじない
pn.extension()
Button
イベントを指定しクリックすると反応が得られるボタン型のウィジェットです。
nameオプションに文字列を渡すとその文字が表示されたボタンを生成できます。
# ボタンウィジェットの生成
pn.widgets.Button(name= "test")
button_type(色)の指定
button_typeを指定すると、ボタンの色を変更できます。
pn.Column(
pn.widgets.Button(name="default", button_type="default"),
pn.widgets.Button(name="primary", button_type="primary"),
pn.widgets.Button(name="success", button_type="success"),
pn.widgets.Button(name="warning", button_type="warning"),
pn.widgets.Button(name="danger", button_type="danger"))
ボタンの高さやフォント・フォントサイズ・上記以外の色への変更はできなさそうです。
(一応、css_classesを使ってクラスにCSSを指定すればフォントなどは変更できそうですが、他のウィジェットも影響を受けてしまいそう)
ボタン押下時のイベント指定
ボタンを押したときの動作を割り当てるには、defでイベント関数を宣言した後、on_clickで引数にイベント関数名を指定するだけです。
以下の例は、ボタンを押すたびにボタンの表示(nameとbutton_type)が変更されるというものです。
# クリック時のイベント規定
def click(event):
# ボタンのタイプがプライマリーだったら、表示を「押すなよ」にしてボタンタイプをサクセスに。
if button_e.button_type == "primary":
button_e.name = "押すなよ"
button_e.button_type = "success"
# ボタンのプライマリー以外だったら、表示を「押せよ」にしてボタンタイプをプライマリーに。
else:
button_e.name = "押せよ"
button_e.button_type = "primary"
# ボタンウィジェットの生成
button_e = pn.widgets.Button(name= "押すとイベント実行")
# clickした時のイベントを指定
button_e.on_click(click)
# 描画
button_e
CheckBox
押すたびにON・OFF切り替わるウィジェットです。
# インスタンス生成
ck = pn.widgets.Checkbox(name= "あなたは神を信じますか")
#表示
ck
ON/OFF状態の取得
チェックボックスの状態はvalueで取得でき、値はTrue/Falseで返されます。
# チェックボックスの値表示
ck.value
↓
True
Select
押すとプルダウンが表示され、その中から一つ選択肢で選ぶタイプのウィジェットです。
引数optionsに選択肢が列挙されたリスト型を指定します。
select = pn.widgets.Select(options=["春", "夏", "秋", "冬"], value="春")
select
初期値の指定
引数のnameでウィジェットと一緒に表示する項目名、valueで初期値を指定可能です。(省略すると一番上のになる)
# 初期値指定なし
s1 = pn.widgets.Select(name="季節:初期値指定なし", options=["春", "夏", "秋", "冬"] )
# 初期値指定あり
s2 = pn.widgets.Select(name="季節:初期値指定あり", options=["春", "夏", "秋", "冬"], value= "秋")
# 表示
pn.Column(s1, s2)
選択されている要素の取得
選択されている中身を取得するときは、valueメソッドで取得できます。
s3.value
↓
'春'
RadioBoxGroup
Select同様複数の選択肢から一つの要素を選ばせるウィジェットとしてはRadioBoxGroupもあります。
# インスタンス生成
radio = pn.widgets.RadioBoxGroup(name= "ラジオボックス", options= ["春", "夏", "秋", "冬"])
#表示
radio
MultiSelect
先ほどのSelectに似たウィジェットで、複数の項目を選択できるウィジェットとしてMultiSelectがあります。
Selectはクリックするとプルダウンが表示される感じでしたが、MultiSelectはプルダウンを広げずに選択肢が表示されています。
そして、Ctrキー, Shiftキー, ドラッグ等を活用して項目を複数選択できます。
# インスタンス生成
m_select = pn.widgets.MultiSelect(name= "季節選択(複数)", options= ["春", "夏", "秋", "冬"])
# 表示
m_select
表示範囲の指定
全部表示させたくない場合は、sizeで表示する要素数を指定します。
# インスタンス生成
m_select_size3 = pn.widgets.MultiSelect(name= "季節(複数選択、サイズ3)", options= ["春", "夏", "秋", "冬"],
size= 3)
# 表示
m_select_size3
値の取得
値はvalueで取得でき、選択されている要素名がリストで返されます。
m_select.value
↓
['春', '秋']
CheckBoxGroup
同様に、複数の選択肢から一つを選択させるウィジェットとしてCheckBoxGroupがあります。
# インスタンス生成
checkbox_grp = pn.widgets.CheckBoxGroup(name= "季節(チェックボックスグループ)", options= ["春", "夏", "秋", "冬"])
# 表示
checkbox_grp
値の取得
結果がvalueで取得でき、リストで返される点も同様です。
checkbox_grp.value
↓
['秋', '冬']
MultiChoice
上記2つと比較して少し動作が異なるウィジェットとしてMultiChoiceがあります。
クリックすると選択肢が出てくることはMultiSelectと同様ですが、文字入力をすると選択肢を絞り込むことができます。
Ctrl押さずとも複数項目選択できるので、PC操作に不慣れなユーザーが触れるケースを想定するとMultiSelectより有用かもしれません(表示項目の順番指定できないのが難点ですが)
# インスタンス生成
m_choice = pn.widgets.MultiChoice(name= "季節(マルチチョイス)", options= ["春", "夏", "秋", "冬","春分の日", "ハルジオン"],
height= 200, )
# 表示
m_choice
heightは指定しておかないと、表示が見づらくなるので、指定しておいた方が良さげです。
選択できる要素数上限の指定
max_itemsで選択できる要素の上限を指定できる点も、MultiSelectやCheckBoxGroupより使いやすいポイントですね。
# インスタンス生成
m_choice_item2 = pn.widgets.MultiChoice(name= "季節(アイテム数上限2)", options= ["春", "夏", "秋", "冬"],
height= 200)
# 選択可能数の上限指定
m_choice_item2.max_items = 2
# 表示
m_choice_item2
IntSlider, FloatSlider
つまみを動かして値を取得するスライダータイプのウィジェットです。
整数型(IntSlider)と浮動小数点型(FloatSlider)があります。
引数のstartで下限値、endで上限値を指定します。
# 整数型スライダーのウィジェット生成
i_slider = pn.widgets.IntSlider(name="Int型スライダー", start= 1, end= 10)
# 浮動小数点型スライダーのウィジェット生成
f_slider = pn.widgets.FloatSlider(name="Froat型スライダー", start= 1, end = 10)
# 表示
pn.Column(i_slider, f_slider)
ステップ数と初期値の指定
stepで幾つずつ刻むか指定できます。
valueでは初期値を指定できますが、スライダーの値はstart値基準でstep値が刻まれるます。そのため、下記例では初期値の5から一度動かしてしまうと、再び5につまみを合わせることが出来なくなっています。
# 初期値とステップ数を指定したスライダー
sl_stvl = pn.widgets.FloatSlider(name= "カスタムスライダー", start= 1, end= 10, step= 0.3, value= 5)
# 表示
sl_stvl
表示形式の指定
値の表示形式はformatで指定できます。
numbroの旧フォーマットというもので指定します↓。
参考サイト:http://numbrojs.com/old-format.html
通常のformatでは表現の幅が少し狭いので、こだわった表示をしたかったら、bokehモデルのPrintfTickFormatterを使います。
formatにPrintfTickFormatterを指定し、さらにその中のformatにPrintf表示形式を文字列で指定します。
printfについては下記サイトが参考になりました。
https://www.mm2d.net/main/prog/c/printf_format-01.html
# フォーマットの指定(小数点以下1桁まで表示)
f_slider2 = pn.widgets.FloatSlider(name="小数点1桁まで表示", start= 1, end = 10,
format="0.0")
# PrintfTickFormatterのインポート
# (bokehはpanelと同時にインストールされるため追加インストール不要です)
from bokeh.models.formatters import PrintfTickFormatter
# フォーマットの指定(100%中の〇〇%と表示)
f_slider3 = pn.widgets.FloatSlider(name="戸愚呂弟", start= 0, end = 120,
format=PrintfTickFormatter(format="100 %% 中の %.1f %%"))
# 表示
pn.Column(f_slider2, f_slider3)
スライダーの見た目のカスタマイズ
その他、バーの色(bar_color)や、縦横の向き(orientation="vertical"で縦向き)などを指定できます。
directionは左から(ltr:Left_to_Right)か右から(rtl:Right_to_Left)かを指定します。
縦向きの場合は下側がRigthとなります。
# カスタマイズしたスライダー
customize_slider2 = pn.widgets.IntSlider(start= 0, end= 10,
bar_color="#FF8080",
orientation="vertical",
direction="rtl",
height=100)
# 表示
customize_slider2
RangeSlider, IntRangeSlider(範囲指定型スライダー)
範囲を指定できるRangeSlider(←浮動小数点型。整数型はIntRangeSlider)もあります。
# 範囲スライダー(整数型)
range_int_slider = pn.widgets.IntRangeSlider(name="Int型_範囲スライダー", start= 1, end = 10)
# 範囲スライダー(浮動小数点型)
range_float_slider = pn.widgets.RangeSlider(name="Float型_範囲スライダー", start= 0, end= 10, step= 0.1)
# 表示
pn.Column(range_int_slider, range_float_slider)
値の取得
RangeSliderの値もvalueで取得でき、左端と右端のタプルで返されます。
print(range_int_slider.value)
(1, 10)
TextInput
入力した文字列を取得するウィジェットです。
placeholoderで未入力の時に表示する文字列を指定します。
# インスタンス生成
textinput = pn.widgets.TextInput(name= "テキストインプット", placeholder= "ここに文字を入力してください。")
# 表示
textinput
高さ・幅の指定
前回書いた記事では「高さを指定できない」と間違ったことを書いてしまいましたが、普通にheight指定できます。
# インスタンス生成
textinput_sizing = pn.widgets.TextInput(name= "高さも幅も変えられます", placeholder= "ここに文字を入力してください。",
width= 100, height= 80)
# 表示
textinput_sizing
入力文字数上限の指定
入力する文字数の上限も指定できます。
# インスタンス生成
textinput_max10 = pn.widgets.TextInput(name= "最大10文字", placeholder= "全角含め最大10文字入力できます",
max_length= 10)
# 表示
textinput_max10
ちなみに、ウィジェットの状態に応じて関数を動作させるdependsデコレータを使用すれば、入力できる文字数の残りを表示する、といったカスタマイズも可能です。
# インスタンス生成
textinput_max100 = pn.widgets.TextInput(name= "文字数のこり:100", placeholder= "全角含め最大100文字入力できます",
max_length= 100)
# テキストボックスの中身が更新されるたびに実行する関数
@pn.depends(textinput_max100.param.value, watch= True)
def length_count(text_value):
text_l = textinput_max100.max_length - len(text_value)
textinput_max100.name = "文字数のこり:" + str(text_l)
# 表示
textinput_max100
完全にリアルタイムではなく、ウィジェット上でEnter押したときに内容が反映されるようです。
FileInput
FileInputを使えば、ユーザーにファイルを選択させることができます。
# インスタンス生成
file_input = pn.widgets.FileInput(name= "ファイル選択")
# 表示
file_input
前回の記事で紹介しなかったのは、選択したファイルを扱うにはひと手間必要だからです。
まず、一応filename使えば選択されたファイルの名前を取得することができるのですが、フルパスでは取得することができません。
そのため、ファイルをデータとして扱うにはvalueを使用する必要があります。
ただし、valueで取得したデータはbytes型となるため、中身のタイプに応じて変換する必要があります。
そのやり方として標準ライブラリioのBytesIOもしくはStringIOを使用してメモリ上に一度保存する方法があります。
選択されたファイルの取り扱い(文字列データ以外のケース)
文字列以外のデータはBytesIOでバイナリデータをメモリに書き込みます。
下記は選択された画像データ(png)を読み込むケースです。
import io
from PIL import Image
# bytesデータを標準ライブラリioのBytesIOを使用しメモリ上に保存
buffer = io.BytesIO(file_input.value)
# メモリ上のファイルを読み込み
im = Image.open(buffer)
# 画像表示
im
選択されたファイルの取り扱い(文字列データのケース)
文字列データ(CSVなど)はStringIOを使用します。
このとき、バイナリデータをいったんデコードする必要があります。デコードでは文字コードを指定する必要があるのですが、無難にutf-8-sig(BOM付きUTF8)を指定しておけば良いと思います。
下記はcsvのデータを取り出しDataFrameに変換するケースです。
import io
import pandas as pd
# 読み込んだファイルのバイトデータをデコード後、StringIO形式でメモリに保存
buffer = io.StringIO(file_input.value.decode("utf-8-sig"))
# 保存したメモリ上のアドレスを参照しCSVとして読み込みDataFrameに変換
df = pd.read_csv(buffer)
# DataFrame表示
df
選択したファイルを同一セルで扱う場合の注意点
一つのセル内でFileInputと表示を行なう場合、「~.valueにデータがない!」とエラーが出るので、Buttonを使用して能動的に変換を行なうか、dependsデコレータを使用してファイル選択がされたと同時に変換を実行するようにする必要があります。(dependsデコレータについては前回記事を参照)
下記はdependsを使用しファイルが開かれると同時に画像が表示される例です。
# インスタンス生成
file_input = pn.widgets.FileInput(name= "ファイル選択", accept=".png")
output = pn.pane.PNG()
# ファイルが選択されると実行される関数
@pn.depends(file_input.param.value, watch=True)
def get_file(f_value):
# バイト列をメモリ上のバッファに格納
buffer = io.BytesIO(f_value)
# バッファを読み込み
output.object = buffer
# 表示
pn.Column(file_input, output)
GoogleColaboratotyでもローカルのファイルを取り扱うことが可能
GoogleColaboratoryでファイルを扱う場合、毎回GoogleDriveと接続してファイルをアップロードするなど、何かと手間がかかるイメージがあります。
FileInputはGoogleColaboratory上でもローカルのファイルをアップロードしてそのまま使用できるので、手っ取り早くファイルを開きたい場合などにオススメです。
今後
今回は、とりあえず自分が使いそうな入力系ウィジェットについてまとめました。
また気になるウィジェットが出てきたら適宜追加していきたいと思います。