.. title:: Pythonで心理実験 - 例題19-7 例題19-7:自由記述って面倒くさいよね ======================================== **B:** うわっ、すごく久しぶりの更新だというのになんて投げやりなタイトルなんだ。読者のみなさんに申し訳ないと思わないんですか! **A:** んあ。そんなことに気ぃ使ってる余裕がないんだ。大体「講義期間が終わったら大学の先生って休みなんですよね?」とか言ってる輩は一体何を考えておるのか。講義期間が終わったら休みというか自由に研究に取り組める世の中だったら大学教員のQOLは10 :sup:`5` 倍くらい改善されるに違いない。 **B:** 10 :sup:`5` 倍とはこれまた盛りましたな。ていうかそんなアホなこと言っていられるようなら大丈夫ですね。いや、声も元気がないし顔色も悪いので正直ちょっと心配しましたが。 **A:** うい。だいたい合ってる。さっさと片付けて(自主規制)の仕事に戻らんといかんから始めるぞ。 **B:** えー、あー…、はい。んじゃ、どうぞ。 **A:** 今日のお題だが、更新が途絶えていた半年ほどの間に何人かの方々からPsychoPyで自由記述させることは出来ないのかというご質問をいただいた。多分4人くらいだったと思うけどはっきり覚えてない。 **B:** 自由記述。紙に書くんならPsychoPyは関係ないだろうから、キーボードか何かから入力させたいということですかね? **A:** まさにそれよ。これは鬼門というかなんというか、正直英語だけでいいんならPsychoPyのメーリングリストにもこれに関するトピックがあって、まあCodeコンポーネントを駆使しまくればBuilderとかでも実現できんことはないのよ。コードを直接書いてももちろんよい。 **B:** ほう。またここにも言語の壁が。おのれ、英語話者め。 **A:** 英語入力の方は練習問題にしてもいいくらいだが…、でもShiftキーとかカーソルキーとか、101とか106とか意識し出すと絶望的に面倒くさくなるのでこれはパス。 **B:** 101? 106? **A:** ああ、知らんのか。ここでは101は英語キーボード、106は日本語キーボードのことだ。それぞれキーが101個、106個あるからそう呼ばれる。もっとも最近のキーボードはWindowsボタンとか追加されていて104個とか109個とかだったりするのだが。 **B:** 106って日本語以外の言語でも106だったりしないんですかね? **A:** 知らん。興味があれば自分で調べたまえ。 **B:** えー。ぼくが調べるわけないじゃないですか。 **A:** 101に基づいたキーボードと106に基づいたキーボードではのキー配置がいろいろ違っていて、例えば@マークを入力する時に106なら@キーを押せばいいが、101ならShiftキーを押しながら2を押さないといけない。この辺りの違いをきちんと吸収しようとするとめちゃ面倒くさい。そしてなにより厄介なのはIME。PsychoPyのKeyboardコンポーネントやpsychopy.event.getKeys()などで日本語入力を受け付けようというのは、もう想像もしたくないくらい面倒くさい。日本人対象の質問紙調査を全数調査でやれって言われるくらい面倒くさい。 **B:** ちょ、それいくらなんでも盛りすぎ…! **A:** どっちも即座に「そんなのやりません」って言うくらい面倒くさいってことだよ。とにかく面倒くさいので、別の方法を考える。それは… **B:** …それは? **A:** 紙に鉛筆で記入してもらえばいいんだよ。 **B:** ほほう!それはそれは…って、あかんがな! **A:** 本気なんだがね。過去の例題でも何度か言ってるけど、どんな泥臭いやり方であれ目的が達成出来る事が大切だ。PCでやることにこだわって時間を浪費しているようではお話にならない。 **B:** まあ確かに前にも言ってましたね… じゃ、この例題はこれにて解散? **A:** んなわけあるかい。ご相談の中にあった例だが、回答の入力に要した時間を測りたいなんて場合なんかは、やはりPCが使えるとありがたい。PCでないと提示できないような刺激に対して回答を求める場合だとなおさらだろう。 **B:** なんだか話がまわりくどいですね。で、結局どうするんですか? 何か解決策がある? **A:** **全画面表示を諦めるのじゃ。** さすれば道は開かれん。 **B:** はい? **A:** PsychoPyで受け取るのが難しいのであれば、他のウィンドウで受け取ればよいのじゃよ。 **B:** …それだけ? **A:** それだけ。 **B:** 他のって、テキストエディタとかでも開いておくんですか? **A:** ほほほ。まあそれでも別に構わんのだが、ここはやはり「Pythonで心理実験」講座なんでな。Pythonで頑張るとしよう。 **B:** やっと本題に入りそうだぞ。 **A:** さて、ではサンプルファイル19-7a.pyを見てもらおうかの。 **B:** …ってアンタ、いきなり? **A:** 19-7a.pyでは、 :doc:`例題19-1 <19-1>` で紹介したpsychopy.gui.DlgFromDictを使ってみた。dictオブジェクトを使ってダイアログを作成し、入力された値をdictオブジェクトから取り出せるのだった。 **B:** ちょっとちょっと、せめてまずサンプルプログラムを示してからにしましょうよ。 **A:** おお、そうじゃの。 + 行番号なしのソースファイルをダウンロード→ `19-7a.py `_ .. literalinclude:: source/19-7a.py :language: python :encoding: utf8 :linenos: :lineno-match: **B:** うむむむ?なんだこれは、どういう実験なんだ。 **A:** 実験の体は成しとらんよ。PsychoPyのウィンドウを開いて「好きな食べ物はなんですか?」 「嫌いな食べ物はなんですか?」「昨晩は何を食べましたか?」と順番に表示して、回答入力用のダイアログを表示するだけだ。 .. figure:: img/19-7-01.png **B:** またいい加減な… **A:** まあ :doc:`例題19-1 <19-1>` と同じだが、paramsというdictオブジェクトを用意して、「答え:」というキーを登録しておく。で、このparamsを引数としてpsychopy.gui.DlgFromDictを呼ぶと、「答え:」という見出しが付いた文字列入力欄があるダイアログが表示されるわけだな。で、その後params[u'答え']とすれば入力された文字列を取り出せるのじゃ。以上。 **B:** なんだ、案外簡単ですね。散々前振りしておいてこれだけとは。 **A:** いやいやいや。そうは問屋がおろさんのじゃよ。ダイアログが表示されたときに、何も入力せずにキャンセルボタンを押したらどうなる? **B:** あっ、回答が「」のまま何事もないように次へ進んでしまう! **A:** じゃろ。それも問題じゃが、そもそも質問文の上にどかっとダイアログが居座っているのは問題だと思わんかね。 .. figure:: img/19-7-02.png **B:** ううっ、確かに。 **A:** psychopy.gui.DlgFromDictオブジェクトは、ダイアログが閉じられた後にOKというデータ属性を参照することが出来る。OKボタンを押して終了していればTrueになるので、それで対処できる。もちろん何も入力せずにOKを押されて終了した可能性もあり得るので、その辺もちゃんとチェックするべきじゃな。 **B:** ふむふむ。 **A:** で、ダイアログが質問文の上に居座る件じゃが、残念ながらこれはpsychopy.gui.DlgFromDictを使う限りどうにもならんのじゃ。 **B:** …ところで質問があるんですが。 **A:** なんじゃね? **B:** いつまでその口調なんですか。 **A:** …。 **B:** …。 **A:** …というわけでダイアログの位置を調整することを考える。psychopy.gui.DlgFromDictにはダイアログの位置を決める機能がないのだが、同じpsychopy.guiモジュールに含まれるpsychopy.gui.Dlgを使うとその辺は解決できる。サンプルファイル19-7b.pyを見て欲しい。 **B:** (え、そこはスルーなの?) + 行番号なしのソースファイルをダウンロード→ `19-7b.py `_ .. literalinclude:: source/19-7b.py :language: python :encoding: utf8 :linenos: :lineno-match: **A:** psychopy.gui.DlgについてはPsychoPyのCoderの公式サンプルを参照してほしいのだが、psychopy.gui.Dlg()を呼んでpsychopy.gui.Dlgオブジェクトを作成した後、addField()およびaddText()メソッドを用いて項目を追加して使う。すべて追加し終わったらshow()メソッドを呼べばダイアログが表示される。10から15行目がオブジェクトの生成と項目の追加、そして31行目でshow()している。 **B:** これって10から15行目のダイアログ作成の処理が関数になっていますけど、何か意味があるんですか? **A:** ふむ。いやらしい指摘をしてくるな。元々、キャンセルボタンが押されてダイアログが終了した場合はOKが押されるまで何度も繰り替えしダイアログを表示し直すようにサンプルを書いていたのだが… **B:** 書いていたのだが? **A:** 面倒くさくなってやめた。 **B:** やっぱり。 **A:** 繰り返しダイアログの作成をする場合は関数になっている方がすっきりするからね。それで関数にしていただけで、それ以上の意味はない。その代わり、データ属性OKを用いてOKボタンが押されずに終了した場合はプログラムを終了するという例にしてみた。 **B:** この32行目のdlg.OKがFalseだった場合に実行される35行目のpsychopy.app.dialogs.MessageDialogというのは? **A:** あー、それはちょっと後回し。先にpsychopy.gui.Dlgの話を済ませてから。まずダイアログに載せる項目だが、addField()メソッドで加えると、DlgFromDictと同様に見出し付きのテキスト入力ボックスが追加される。addText()の場合はいわゆるスタティックテキスト、単なるラベルだけが追加される。11行目から15行目と、下図の実行例をよく見比べて欲しい。addText()で追加されている「以下はおまけ」という文字列はラベルとして表示されているのがわかる。 .. figure:: img/19-7-03.png **B:** ええと、addFieldの第二引数はエディットボックスの初期値ですかね。choicesという引数にリストを渡すとプルダウンリストになるんですね。これは便利。 **A:** だろ。そしてもう一つ注意してほしいのが11行目。psychopy.gui.Dlg()に引数posを与えると、ダイアログの左上の座標を指定できる。これによって質問文にダイアログが重なってしまうのを防ぐことが出来るのは上のスクリーンキャプチャの通り。 **B:** こいつが目的でしたね。無事達成、と。 **A:** 最後にダイアログに入力されたデータの受け取り方法。psychopy.gui.Dlgではdataというデータ属性にリストとして入力値が保持されている。データの順番はダイアログの作成時にaddFieldした順番に対応しているので、今回の場合はdata[0]が「答え:」というエディットボックスに入力された値。33行目のように使うことが出来るので確認してほしい。 **B:** ふむふむ。なるほど。 **A:** さて、それでは次のサンプルだが… **B:** AさんAさん、psychopy.app.dialogs.MessageDialogは? **A:** おっと、忘れてた。こいつを使うはちょっと **裏ワザ** なんだが、元々PsychoPy BuilderやCoderで警告などのダイアログを表示するために使われるクラスだ。こういう風にBuilderやCoderを離れて単独で使うことは恐らく想定してないと思うんだが、便利なので使ってみた。 **B:** はあ、裏技ですか。 **A:** 引数parentは、wxpythonのアプリケーション上から呼び出す時に必要になるもので、ここでは呼び出し元のwxpythonアプリケーションがないのでNoneを指定しておく。messageに表示したい文字列、titleにダイアログのタイトルバーの文字列を指定する。typeというのはInfoかWarningで、InfoならOKボタンのみダイアログとなり、Warningならはい、いいえ、キャンセルの三つのボタンがあるダイアログが表示される。ここでは「OKが押されなかったから終了するよ」って通知するだけなのでInfoでいい。 **B:** どのボタンが押されたかわかるんですか? その、Warningだった場合は。 **A:** こらこら、先回りしない。psychopy.app.dialogs.MessageDialog()で作成したpsychopy.app.dialogs.MessageDialogオブジェクトは、ShowModal()メソッドを呼びだすことで実際に表示される。そして、ShowModal()の戻り値がB君のさっきの質問の答え、押されたボタンを表している。wx.ID_YES、wx.ID_NO、wx.ID_CANCELという戻り値がそれぞれ戻ってくる。 **B:** wx.なんちゃらっていうぐらいだからwxというモジュールで定義されている何かですか? **A:** 説明の手間を省いてくれるじゃないか。その通り。import wxとすると使うことが出来る定数だ。 **B:** へへへ。 **A:** さて、実際に実行してキャンセルしてみよう。この通りダイアログが表示されて、OKを押したらプログラムが終了する。 .. figure:: img/19-7-04.png **B:** ええと、キャンセルが押された時の対策。ダイアログの表示位置の指定。さっき出てきた問題はこれで解決だと思いますが、さっきAさん次のサンプルって言いましたよね? **A:** うむ。まあこれでだいたい良いっちゃあ良いんだけど、細かいことを言い出すといろいろキリがない。例えば、今回は「好きな食べ物は?」程度の質問なわけだが、もっと何百文字も書かないといけないような質問だとどうすればいいか。こんな小さいエディットボックスに入力しろというのは無茶すぎる。それに、入力ダイアログが毎回現れたり消えたりするのが鬱陶しいと思わないか? いちいちマウスに手を伸ばしてOKをクリックするのは面倒くさくない? **B:** んー。そんなこと言われても、こんなもんだとしか思ってなかったので。 **A:** 次のサンプルでは、PsychoPy.guiモジュールで使われているダイアログの大元となっているwxpythonを直接利用する。wxpythonについていちいち解説してたら大変な分量になってしまうので、その辺はwebで検索してほしい。 **B:** えー。Tkinterの時はある程度詳しく解説したくせに。 **A:** い・そ・が・し・いっつってんだろ! 気力があるうちに一気に仕上げないと。 **B:** しょうがないおっさんだなあ。 **A:** で、こちらがそのサンプル。19-7c.pyだ。 + 行番号なしのソースファイルをダウンロード→ `19-7c.py `_ .. literalinclude:: source/19-7c.py :language: python :encoding: utf8 :linenos: :lineno-match: **B:** んんと、これは今までのとずいぶん違うな。15行目から47行目が、その、wxpythonとかいう奴ですかね。 **A:** そうだな。逆に言うと49行目以降は19-7a.pyや19-7b.pyとあまりやってることは変わらない。ここでちょっと本当に申し訳ないんだが、wxpythonのwx.Frameを使ったダイアログの作成について初歩的なことは知っているという前提で話をさせてもらう。 **B:** えー。 **A:** いつか機会があればちゃんと解説するから。 **B:** 機会があれば、ね…。(ぼそぼそ) **A:** このサンプルのポイントは、まず39行目のOnOKButton()メソッド。OKボタンを押したときに、ダイアログを破棄せずにExitMainLoop()メソッドを呼んでいる。これによって、60行目のMainLoop()メソッドで開始したダイアログを保持したまま61行目に進むことが出来る。 **B:** ホントに基本すっ飛ばしてきたな… **A:** あともう一つ重要なポイント。17行目でwx.Frame()を呼んでダイアログを作成する際、styleにwx.CAPTIONのみを指定してる。これによってダイアログのタイトルバー右上の閉じるボタンも最小化、最大化のボタンもないダイアログを作ることが出来る。これで想定外の方法でダイアログが閉じられてしまうのを防ぐ。 **B:** …(ついていけないので傍観している) .. figure:: img/19-7-05.png **A:** ついでに回答の入力欄も大きくなっていることに注目してほしい。22行目のwx.EXPANDの指定でエディットボックスが余白全体に拡大するように指定しておいて、34行目のSetSizeでダイアログの大きさを指定している。34行目で指定するサイズを大きくすると、それに応じてエディットボックスも大きくなる。 **B:** (面倒くさそうに)一応聞いておきますが、34行目に使われているappWidthとappHeightという変数がダイアログの大きさの指定に使われているわけですね? 12行目と13行目ですか、定義は。 **A:** その通り。ついでにもうひとつテクニックを紹介しておくと、32行目と33行目でwx.SystemSettings.GetMetricというメソッドを用いて現在のスクリーンのサイズを変数wとhに取得している。このw,hとappWidth、appHeightを使えば確実にダイアログをスクリーンの左右中央に配置することが出来るわけだね。上下方向は、もちろん質問文を避ける必要があるので上下中央より200pix下げてある。 **B:** 下げるのに200足すんですか。35行目のMove()ってのがダイアログの位置を決めてるんですよね? **A:** Move()の役割についてはその通り。「下げるのに足す?」ってのは、PsychoPyと逆だってのを聞いてるんだよね? **B:** はい。まあ何となく答えは想像が付きますが… **A:** 言ってみ。 **B:** 左上が(0,0)で、左と下が正の方向ってことなんですよね? **A:** その通り。なんだ、もっと変化球な答えが来るかと思ったのに。 **B:** 変化球とか言われましても。 **A:** えーと。後は42行目のOnKeyCharの説明もしておかないといけないな。これは23行目でエディットボックスにEVT_KEY_DOWNというイベントが生じたら呼びだすようにバインドしてあるコールバック関数だ。 **B:** コールバック。最近の例題で聞きましたね。 **A:** :doc:`例題21-3 <21-3>` 参照。要するにキーが押されたときに呼び出される。で、やってる内容がGetKeyCode()メソッドで押されたキーを取得して(43行目)、もしキーがEnterだったらダイアログを抜けるというもの。あ、wx.WXK_RETURN, wx.WXK_NUMPAD_ENTERってのがwxpythonでのキー名ね。ふたつあるのは… **B:** テンキーのEnterもあるから、ですか。NUMPAD_ってそういうことですよね。 **A:** うむ。難しい話題になると会話を成り立たせるためにどんどんB君が優秀になるな。筆者のストーリー力が無いのが歴然だ。 **B:** またそういうこと言ってると(自主規制)されますよ。 **A:** ぜひ19-7c.pyを実行して確認してほしいのだが、このコールバック関数によって、いちいちマウスでOKをクリックしなくてもEnterを押すだけで入力を終了できるのだ。マウスで操作させると、どうしても **PsychoPyのウィンドウの方をクリックしてしまったりしてダイアログが行方不明になってしまう参加者さんが出て来る** 。キーボードの操作に限定すれば、そういうトラブルはある程度防ぐことが出来る。 **B:** でも、これってうっかり途中でタタン!とEnter押しちゃいそうですね。 **A:** うむ、それはそうなのだが… **B:** それにこれ、長文入力の際に改行したくなったらどうするんですか? Enter押すと入力終わっちゃいますよね? **A:** それは…、その…、うむむ。 **B:** 普通にウィンドウを操作してダイアログを選び直せばいいだけですし、マウスの方がいいのでは? **A:** それはだな、これは「こんなことも出来ます」という例題であってだな… **B:** ふっ、逃げましたね。 **A:** くそっ、B君に言い負かされるとは。 **B:** ま、ぜひ読者の皆様には23行目をコメントアウトしてこの機能を無効にしていただいて、どちらが自然に操作できるか確認していただきたいものですな。ねぇ、Aさん? **A:** くーっ、くやしい! **B:** 他に何か解説しておくことはありませんか? **A:** えーと、えーっと。68行目! 19-7c.pyではダイアログがいちいち出たり消えたりするのが鬱陶しいのをなんとかしたいというのが目的のひとつだったわけだが、出たり消えたりしてくれた方がありがたいこともある。そういう時にはHide()メソッドを呼ぶとダイアログを隠すことが出来る。もう一度表示させたいときにはShow()を呼べばいい。具体的に言うと、19-7a.pyや19-7b.pyのように回答入力後に毎回ダイアログが消えて欲しい場合は60行目のMainLoop()の直前にShow()、直後にHide()すればいいだろう。 **B:** いやはや、なんだか途中から急に理解力がUPしたのでアレでしたが、このwxpythonってのは奥が深そうですね。僕にとっては十分手を出したくない難しさのような…。 **A:** wxpythonは私もよくわかってないよ。そういうのをわからなくても実験を作れるようにpsychopy.guiモジュールのようなものがあるわけだね。出来るだけ既存のモジュールで済む範囲で実験を組めればいいんだけど、なかなかそうもいかない時がある。そういう時に、今回の例題が自分なりの方法を模索するきっかけになればうれしいね。 **B:** なんだかまとめにかかってますが、記入にかかった時間を測るとかってのはまだ実装されていませんよね? それにサンプルを実行すると「記録しました」って画面に出てきますが、保存に関する処理が見つからないような… **A:** そりゃーもう、その辺りは散々今までの例題でやってきたことだから、その辺は読者の皆さんが頑張ってほしいところよ。例題4とか例題11とか。 **B:** その反応時間の件なんですが、フルスクリーンモードを解除して、どの程度正確に反応時間を測れるもんなんですかね? 今回は入力が終わってからダイアログを閉じるなりなんなりして自分のスクリプトに処理が返ってくるまでにはどうしても遅れがありますよね? **A:** そりゃあるに決まってるが、それに対しては :doc:`例題11-2 <11-2>` で答えたことを繰り返すだけだ。キーボードを用いた自由記述反応の、試行間の分散はどの程度あるだろうか? そして、その分散に対してダイアログから処理が返ってくる遅延時間の分散はどの程度あるだろうか? 私には処理が戻ってくる遅延の方がはるかに小さいだろうと思われるし、仮にその遅延が本当に問題になる現象を扱っているのであれば、多分PCなんかを使って計測をしている場合ではないと思う。細部に気を配ることは大切だが、その辺りのバランスをよく考えて方法を選んでほしい。 **B:** あらら、今回はAさんダメダメで終わるかと思いましたが最後にキリッとしましたね。 **A:** うむ。ちょっとホッとした。もう夜も遅くなってきたし、そろそろ帰らなくては。ではでは、また。