【自分用メモ】Pythonでデータサイエンスをするときに役立ちそうなTips: yield, partial, map, filter, reduce, enumerate

※本記事はData Science from ScratchのChapter2に出てきたTipsの自分用メモです。
参考になりそうなリンクと簡単な使い方だけ書いてます。(Python3です)

yield

yieldについてのイメージは、こちらのブログがわかりやすく、その後こちらのブログを参考にすると理解が深まります。
簡単に言うと、「returnを使うとその行で処理が終わって値をすべて戻してしまうので、膨大なデータを用いた繰り返し作業を行う場合、繰り返しごとにメモリを無駄に消費して大変だよね。そういうときにはyieldっていう便利な物があるんだよ」ということだと思います。

yield.py
def func():
    a = 10
    b = 20
    c = a + b
    yield c      # ここで一旦停止

    a = 100
    b = 200
    c = a + b
    yield c      # ここで一旦停止

    a = 1000
    b = 2000
    c = a + b
    yield c      # ここで一旦停止


for x in func():
    print (x)

#---- 結果 -----
# 30
# 300
# 3000
#---------------

partial

When passing functions around, sometimes we’ll want to partially apply (or curry) functions to create new functions.

「関数回してると時々関数の一部を使って新しい関数を作りたい時あるよね〜」な人に役立つのがpartialだそうです。

使用時にはfunctoolsからimportする必要があります。

exp.py
# 例えば base を power乗 する関数を作る
def exp(base, power):
    return base ** power

これを使って2のx乗を計算する関数を作る場合こんな風に書きますよね。

two_to_the.py
def two_to_the(power):
    return exp(2, power)

でもpartialを使うとこんな風に書けます。

partial.py
from functools import partial
two_to_the = partial(exp, 2)   # 最初の引数で使用する関数、2つ目の引数でbaseを指定
print two_to_the(3)            # 8

参照する関数の別の引数を指定したい場合は、その引数の名前を含めて指定します。

latter_arg.py
square_of = partial(exp, power=2)   # 今回はpowerを指定
print square_of(3)                  # 9

このpartialすごく便利らしいので以下の説明でもpartialを使って慣れようと思います。

Pythonの基本的な高階関数(higher-order functions)

map, filter, reduceは高階関数と呼ばれるものだそうです。

高階関数とは、英語の「 higher-order function 」の訳で、「他の関数を引数として受け取る関数」のことです。たとえば、リストの各要素に共通の処理を加えたリストを作成したい場合なんかに、処理をわかりやすく簡潔に記述することができます。
by LIFE WITH PYTHON

map

map(x, y)x(i) for i in yです。

map.py
def double(x):
    return 2 * x

xs = [1, 2, 3, 4]

twice_xs = [double(x) for x in xs]   # [2, 4, 6, 8]
twice_xs = map(double, xs)           # same as above

list_doubler = partial(map, double)  # *function* that doubles a list
twice_xs = list_doubler(xs)          # [2, 4, 6, 8]

複数の引数を持つリストが与えられていれば、複数の引数を持つ関数にも使用することができるそうです。

multiple_arg_map.py
def multiply(x, y):
    return x * y

products = map(multiply, [1, 2], [4, 5])   # [1 * 4, 2 * 5] = [4, 10]

filter

filter(x, y)i for i in y if x(i)です。

filter.py
def is_even(x):   # True if x is even, False if x is odd
    return x % 2 == 0

x_evens = [x for x in xs if is_even(x)]   # [2, 4]
x_evens = filter(is_even, xs)             # same as above

list_evener = partial(filter, is_even)    # *function* that filters a list
x_evens = list_evener(xs)                 # [2, 4]

reduce

複数の要素を1つにまとめたいときに使う。
具体的には配列の先頭から2つの要素を取り出して処理を行い、次はその結果とその次の要素に処理を行う…ということを繰り返すそうです。
python3からはfunctoolsからimportしないとダメになったそうです。

reduce.py
from functools import reduce

def multiply(x, y): return x * y

x_product = reduce(multiply, xs)           # = 1 * 2 * 3 * 4 = 24

list_product = partial(reduce, multiply)   # *function* that reduces a list
x_product = list_product(xs)               # 24

enumerate

ループ処理を行うときにenumerateを使うと、要素のインデックスと要素の両方を同時に取得できます。

例えばdocumentsというリストの要素とインデックス両方にdo_somethingという処理を繰り返し行う場合

do_something_1.py
 # not Pythonic
for i in range(len(documents)):
    document = documents[i]
    do_something(i, document)

とすれば良いのですが、Pythonicじゃないそうです。
なので、enumerateを使い(index, element)のタプルを作ります以下のようにします。

enumerate.py
for i, document in enumerate(documents):
    do_something(i, document)

仮にインデックスのみを取得したい場合は以下のようにします。(2行目が好ましい)

enumerate_index.py
for i in range(len(documents)): do_something(i)     # not Pythonic
for i, _ in enumerate(documents): do_something(i)   # Pythonic

enumerateとzipの違い

enumerate_vs_zip.py
a = [a1, a2, a3]
b = [b1, b2, b3]

# enumerate
for i, a_i in enumerate(a):
    print(i, a_i)

#---- 結果 -----
# 0 a1
# 1 a2
# 2 a3
#---------------


# zip
for a_i, b_i in zip(a,b):
    print(a_i, b_i)

#---- 結果 -----
# a1 b1
# a2 b2
# a3 b3
#---------------

参照

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.