pythonのmapとlambdaの使い方について、fastqファイルを使って説明します。
以下のテストファイルは、bioinformaticsではおなじみのfastqファイルとよばれるDNAシーケンサーの解析結果のファイルで、@行はheader、次の行がDNAの塩基配列、3行目に+を挟んで、4行目は2行目のDNA塩基配列の一文字一文字に対する品質評価値になっており、品質評価値に33を足した数値がASCII文字で変換された数になっています。
@test1
GAGCACACGTCTNNANNCNAGTCANNANNNANNNNNNNNNNANNCNNNNNNTNNNNNNNNANNNNTGTCCATTGCNNNCACATCATTGTTTACTTGCGCNT
+
;<<:?@9<?############################################################################################
品質評価値をもとの数値に直したい。そこでpythonでごちゃごちゃ書こうとしたのだけれど、とびきり便利なmapとlambdaの組み合わせに出会ったのでメモします。 ちなみに、環境はpython2です。
メモ: pythonのversion間の高階関数の使い方の違いについて。
たとえば、Aという品質評価値を数値に変換するには、pythonではアスキーコードを変換するord(反対はchr)という組み込み関数をつかって、数値に変換し、それから33を引けば元の値になります。
> python -c 'print ord("A")-33'
32
これをtestファイルの品質値の行にある101文字全て変換するには、for文を使うと
asci_string = ";<<:?@9<?############################################################################################"
for baseq in asci_string:
score = ord(baseq) - 33
print score,
と書くことができます。
26 27 27 25 30 31 24 27 30 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
この値は大きいほど品質が良いことを示しますので、"#"の品質値の塩基はとても品質が悪いことがわかります。さて、このASCIコードの変換プログラムの書き方はfor文を使っていて、プログラムが長くなるにつれて縦に長いコードになるのは辛いですし、実行速度も遅そうです。そこで、mapを使って表現してみます。
asci_string = ";<<:?@9<?############################################################################################"
def convert_func(x):
score = ord(x) - 33
return score
res_score = map(convert_func, asci_string)
print res_score
[26, 27, 27, 25, 30, 31, 24, 27, 30, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
このようにfor文を回避しても関数を定義する行の文長くなってしまいます。
そこで、lambdaという無名関数を使って、mapの中に"convert_func"関数に相当する処理を書いてしまえるということを知りました(いままで知らなかったんかい!)。次のようになります。
asci_string = ";<<:?@9<?############################################################################################"
res_score = map(lambda x:ord(x) - 33, asci_string)
print res_score
文字列は、for文やmapでは内部で1文字に分割されてイテレートされる。という情報をいただきました。ありがとうございます、修正させていただきました。
asci_string = ";<<:?@9<?############################################################################################"
asci_list = list(asci_string) #この処理(リスト化)を行う必要がありませんでした。
res_score = map(lambda x:ord(x) - 33, asci_list)
print res_score
どうでしょう。一行でかけてしまいました。mapはリストを返してくれるので結果は同じです。無名関数は、一度だけしか使われない使い捨ての関数のことです。一度きり使うだけなので、名前をつける必要がないため無名関数と呼ばれています。lambda式を使って無名関数を作る書式は次のようになります。
lambda 引数(例ではx):返り値 (例では、ord(x) - 33)
例では、xが引数でmapからasci_listの値を一つ一つ受け取り、指定した処理を実行してから返り値を返してくれます。これはとても便利ですね!