.. title:: Pythonで心理実験 - 例題3-3 例題3-3:リストの料理法 ========================== **A:** よし、続いて今度はリスト関連の演算子だ。リストの演算子は今までに少しずつ解説してきたので、実はあまり話す事がない。 まあ簡単にまとめておこう。 .. csv-table:: :delim: $ [ ]$リストを生成する。 a[x]$リストaのx番目の要素を取り出す。 a[x:y]$リストaのxからy番目の要素を取り出した部分リストを生成する。xまたはyが省略された時はそれぞれ最初から、最後までと解釈される。 a[x:y:s]$リストaのxからy番目の要素をs番目毎取り出した部分リストを生成する。xまたはyが省略された時はそれぞれ最初から、最後までと解釈される。 a + b (bはリスト)$aとbを連結したリストを生成する。 a \* b (bは整数)$aをb回繰り返したリストを生成する。 x in a $xがリストaに含まれていればTrue、なければFalse。 a == b (bはリスト)$aとbの要素が等しければTrue、そうでなければFalse a < b など$aとbの要素を先頭から辞書順で比較する。 **B:** えーと…全部聞いたことありましたっけ? **A:** a+bとかa\*bは初めてだったかな? インタプリタで試してみればすぐわかる。x in a もインタプリタで試してみるといいよ。 :: >>> a = [1, 2, 3] >>> a [1, 2, 3] >>> b = a + [4, 5] >>> b [1, 2, 3, 4, 5] >>> c = a * 4 >>> c [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3] >>> 1.4 in a False >>> 2 in a True **A:** inは特定のデータがリストに含まれているかどうか知りたい時にすごく便利なのでぜひ押さえておきたいね。 **B:** ふむふむ。a-bやa/bはないんですか? **A:** ない。 **B:** そんなあっさりと。 **A:** じゃあ、実数でa-bと言えばa+(-b)なわけだが、a+(-b)の(-b)に相当するリストって何か思いつくか? **B:** ぐっ…。「bに含まれているものを除く」とか。 **A:** 単にbの要素を除くといっても、a+(-b)の演算でリストaにリストbの要素が全く含まれていなかったり、複数個含まれていたりすると厄介だよな。 まあ適当にルールを決めて(-b)を定義する方法もあるかも知れないが、pythonはそうしなかったわけだ。リストの要素を取り除いたりするには以下のようなメソッドを使う。 .. csv-table:: :delim: $ a.append(x)$リストaにxを最後の要素として追加する。 a.extend(x)$リストaにリストxを連結する。xはリスト、タプル、文字列など要素が並んだデータ型でなければならない。 a.insert(i,x)$リストaのi番目にxを挿入する。 a.remove(x)$リストaの中で、先頭からチェックして最初に現れたxを削除する。もしxがひとつもなければエラーになる。 a.pop(i)$リストaのi番目の要素を削除して戻り値として返す。iが省略されればaの最後の要素を削除して戻り値として返す。 a.index(x)$リストaの中で、先頭からチェックして最初に現れたxがaの何番目の要素であるかを返す。もしxがひとつもなければエラーになる。 a.count(x)$リストaにxが何個含まれているかを返す。 a.sort()$リストaの要素を並び替える。 a.reverse()$リストaの要素を現在の逆順に並び替える。 **B:** メソッド? **A:** クラスの扱い方を解説する時にきちんと説明するよ(注:例題5-3、5-4)。とりあえずクラスが持っている関数のことだと思っときゃ大体大丈夫だ。 **B:** あー、またそういういい加減な事を。 **A:** うるさい。append()は実験条件のリストを作成したり、実験結果をリストに保存する時に便利だから覚えておくといい。 例えば試行毎にいくつかの変数の内容を保存しておきたいとする。こういう場合は最初に空のリストを作っておいて、一試行終わる毎にこのリストに保存しておきたい変数のリストをappendする。 こんな感じだな。 :: results = [] while trial < MaxTrial: #ここに刺激を提示したりや反応を記録したりする処理があるとする results.append([trial, condition, response]) **B:** trial、condition、responseが残しておきたい変数ですか? **A:** その通り。 **B:** 何でリストにしてappendするんですか? **A:** そうしておくと、後からresultsから値を取り出したい時に便利だからさ。results[n]とすれば第n試行のデータをまとめて取り出せるし、results[n][2]とすればこの例の場合第n試行の反応を取り出せる。 **B:** はああ。なるほど。 **A:** リストに関しては本当にいろいろなテクニックがあるけど、ここでは内包表記を説明しておこう。 このプログラムを実行するとaにはどのようなリストが格納される? :: a = [] for x in range(5): a.append(0) **B:** ええと、まずaに空のリストを代入。次のfor文は…range(5)は[0 1 2 3 4]だから、xにこのリストの値を順番に代入しながら字下げされている文を繰り返す。 字下げされてるのは…aに0を追加する? xの値に関係なく0を追加するっていうことですか? **A:** そうだね。それで正しい。 **B:** じゃあ、a.append(0)が5回繰り返されることになるから、[0,0,0,0,0]になると思うんですが。 **A:** 正解。長さが5で値がすべて0のリストが出来る。たったこれだけの事に3行も使うのはもったいなくないかね? **B:** へ? べつにお金がかかるわけじゃないので構いませんが。 **A:** そーいう話の腰を折るような事を言うな。ともかく、この処理は以下のように1行で書く事が出来る。インタプリタを使おう。 :: >>> a = [0 for x in range(5)] >>> a [0, 0, 0, 0, 0] **B:** うわ、なんだこりゃ。かえって分かりにくいです。 **A:** 普通に3行使って書く方法も同じことだから、どちらを使うかは好みの問題だ。[ for リスト]と書くと、for文を繰り返した結果を要素とするリストが生成される。 他の例も挙げておこう。この例ではリストbの要素を2乗したリストを生成する。 :: >>> b = [7, 2, 5] >>> [x**2 for x in b] [49, 4, 25] **A:** 次の例ではsin(x)の値を0から1まで0.1刻みで並べたリストを生成する。もちろんsin(x)はimport済みとするよ。 :: >>> [sin(0.1*x) for x in range(11)] [0.0, 0.099833416646828155, 0.19866933079506122, 0.2955202066613396, 0.389418342 30865052, 0.47942553860420301, 0.56464247339503548, 0.64421768723769113, 0.71735 609089952279, 0.78332690962748341, 0.8414709848078965] **B:** range(11)? なんで10じゃなくて11なんですか? **A:** range(10)とrange(11)を実行してみたまえ。後で0.1倍して0から1になるようにしないといけないんだから、 range()関数で作成するリストは0から10までじゃないといけない。 :: >>> range(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> range(11) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] **B:** あ、そうか。ややこしいなあ。 **A:** まあ、この辺りは慣れだね。そのうちパッと判断できるようになるよ。 **B:** あ、そういえば。 **A:** ん?どうした? **B:** 音を鳴らす方法を教えてもらった時(注:例題2-1)、同じようにsin(x)のリストを作りましたよね。その時ナントカ言う変な関数を使いましたけど、 あれもこの内包表記とかいうのを使ったらナントカ言う関数を使わなくて済むんじゃないんですか? **A:** ナントカ? なんだったか。ええと、例題2-1は、と。ああ、numpy.linspace()のことか。 **B:** そうそう、そんな感じの。 **A:** ふむ。よく覚えていたなあ。なかなか良い質問だ。結論から言うと、ただsin(x)の値をずらっと並べたリストを作るだけだったら内包表記で十分だ。 しかし、例題2-1のようにそのあと演算をしたいのならnumpy.linspace()の方が良い。 **B:** 何が違うんですか? **A:** 内包表記で作られるのは普通のリストだが、numpy.linspace()で作られるのはarrayクラスのインスタンスだ。 リストとarrayクラスでは演算子の働きが違う。linspace()でarrayのインスタンスを作ってまず変数aに保存する。 :: >>> from numpy import linspace >>> a = linspace(0,1,11) >>> a array([ 0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ]) **A:** array型のインスタンスは、+や\*を使って個々の要素に値を加えたり掛けたりする事が出来る。 :: >>> a+5 array([ 5. , 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8, 5.9, 6. ]) >>> a*7 array([ 0. , 0.7, 1.4, 2.1, 2.8, 3.5, 4.2, 4.9, 5.6, 6.3, 7. ]) >>> a/1.7 array([ 0. , 0.05882353, 0.11764706, 0.17647059, 0.23529412, 0.29411765, 0.35294118, 0.41176471, 0.47058824, 0.52941176, 0.58823529]) **B:** ふむふむ。 **A:** ところが内包表記で作られる普通のリストでは、+の後に定数が続く演算はエラーになるし、\*は全く違う意味になる。 /は演算子自体定義されていない。 :: >>> a = [x for x in range(11)] >>> a [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> a + 5 Traceback (most recent call last): File "", line 1, in TypeError: can only concatenate list (not "int") to list >>> a * 7 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> a / 1.7 Traceback (most recent call last): File "", line 1, in TypeError: unsupported operand type(s) for /: 'list' and 'float' **B:** うーん。なんだかよく分かりませんが、とにかく違うんですね。 **A:** よくわかりませんがじゃ困るんだが、まあそういう事だ。普段は普通のリストでプログラムを書いて、 「あれ、これ足せないのか」とかいう事で困ったらarrayを使う事を検討したらいいんじゃないかな。 **B:** どんな時に困るんでしょうかね? **A:** そんなもんわからんよ。まあ、被験者の反応とかをリストに保存していて、それにプログラム中で何らかの処理を行いたい時とかにはarray型にしたくなる時があるかな。 **B:** そういう時っていちいちリストをarrayに変換しないといけないんですか? **A:** もちろんそうだが、その作業自体は簡単だ。numpy.array()という関数を使えばいい。 次の例では普通のリストaをarray()関数を使ってarrayクラスのインスタンスに変換してbに格納している。a + 5はエラーになるがb + 5はエラーにならない。 :: >>> from numpy import array >>> a = [x for x in range(11)] >>> a + 5 Traceback (most recent call last): File "", line 1, in TypeError: can only concatenate list (not "int") to list >>> b = array(a) >>> b + 5 array([ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) **B:** むむー。 **A:** aの中に文字列などのデータがあると、数字もすべて文字列として変換されてしまう。 そうすると5を足そうとしても「文字に数値なんて足せねえ」ってエラーが出てくる。まあこの辺りは初級の範囲を超えてるかな。 ええと、後話しておくべき事は、と…。そうだ、辞書型についても少し話しておくか。 **B:** 辞書型? **A:** 心理実験ではわざわざ意図的に使う必要はないと思うんだけど、VisionEggなどのクラスを使っていると暗黙のうちに使用しているから。 リストというのは要素が順番に並んでいて、「x番目の要素」という具合に数字(インデックス)を使ってその要素を指し示す。 これに対して、辞書(dictionary)というのはキーと呼ばれる値を使って対応する値を取り出す事が出来る。まあ例を挙げるとこんな感じだ。 :: >>> a = {'height':173, 'weight':68, 'from':'Japan'} >>> a['from'] 'Japan' >>> a['height'] 173 **A:** 簡単に解説しておくと、まず辞書を作成して変数aに格納している。辞書は{ }を使って作成する点と、 ひとつひとつの要素が : で区切られた二つの値から成る点がリストと異なる。この : で区切られた前の値がキーと呼ばれるもので、キーを使うと キーと対になっている値を取り出す事が出来る。みんなが辞書を使う時は「25417番目の単語を調べよう」なんて使い方はしなくて、「dictionaryという単語を 調べよう」っていう使い方をするだろう? それと同じような感じで使用できる。 **B:** a['from']って書いてますけど、ここは{ }じゃないんですか? **A:** 辞書を作る時は{ }だが、参照する時は[ ]を使う。ちなみに辞書を要素とする辞書も作る事が出来る。 :: >>> TaroData = {'height':173, 'weight':68, 'from':'Japan'} >>> MikeData = {'height':181, 'weight':75, 'from':'USA'} >>> d = {'Taro':TaroData, 'Mike':MikeData} >>> d['Taro']['height'] 173 >>> d['Mike']['from'] 'USA' **B:** あー。ちょっと便利そうかも。リストで書いてると3番目の要素って何だったけ?ってわからなくなって 時々困ってたんですが、'height'や'from'とかって指定出来れば間違えませんもんね。 **A:** まあそれだけならindexFrom=2とかいう具合に変数に何番目の要素か代入しておけばいいんだがな。 リーディングスパンテストのサンプルプログラムでも使っていた手だね。 心理実験のプログラミングで辞書を使わないと難しい処理ってのはぱっと思いつかないんだけど、 いずれ紹介する関数では辞書を使った重要テクニックもあるので一応紹介しておく(注:例題5-2のキーワード引数)。 **B:** それにしても演算子だけでもう3回も費やしてしまうなんて、ずいぶん奥が深いですねえ。 **A:** これでもかなり端折っているんだがな。演算子がらみであともう一回話をするぞ。次回のテーマは文字列だ。 **B:** うえー。まだ続くんですか。