CUBE SUGAR CONTAINER

技術系のこと書きます。

Python: pandas の Series#apply() で複数カラムの特徴量を一度に作る

今回は、複数カラムの特徴量を一度に作りたいなーっていう、たまに思うやつを書く。 結論から先に書いてしまうと、返り値を Series にしてやれば良い。

使った環境は次の通り。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.3
BuildVersion:   18D109
$ python -V
Python 3.7.2
$ pip list | grep pandas
pandas          0.24.2

下準備

まずは下準備として pandas をインストールしておく。

$ pip installp pandas

サンプルデータとしてくだものの名前が入った DataFrame を用意する。

>>> import pandas as pd
>>> data = [
...   ('Apple'),
...   ('Banana'),
...   ('Cherry'),
... ]
>>> df = pd.DataFrame(data, columns=['name'])

複数カラムの特徴量を作る

例えば n-gram の特徴量をくだものの名前から抽出することを考える。

そのために、次のような n-gram を計算して配列として返す関数を定義する。

>>> def n_gram(x, n=1):
...     return [x[i : i + n] for i in range(len(x) - n + 1)]
...

これを先ほどの DataFramename カラムに適用すると、次のようになる。

>>> df.name.apply(n_gram)
0       [A, p, p, l, e]
1    [B, a, n, a, n, a]
2    [C, h, e, r, r, y]
Name: name, dtype: object

ちゃんと unigram が計算できている。

bigram を計算したいときは、次のように引数に 2 を指定すれば良い。

>>> df.name.apply(n_gram, args=(2,))
0        [Ap, pp, pl, le]
1    [Ba, an, na, an, na]
2    [Ch, he, er, rr, ry]
Name: name, dtype: object

でもこれ、めんどくさいから一度に計算したくない?ってなる。 なったときは、次のように各 n-gram を計算して Series として返すラッパーを書いてやる。

>>> def multiple_n_gram(x, n_max=3):
...     return pd.Series([n_gram(x, n=i)
...                       for i in range(1, n_max + 1)])
... 

上記のデフォルトでは trigram まで計算する。

これを、先ほどと同じように適用してみる。 すると、三次元の特徴量が一度に抽出できた。

>>> df.name.apply(multiple_n_gram)
                    0                     1                     2
0     [A, p, p, l, e]      [Ap, pp, pl, le]       [App, ppl, ple]
1  [B, a, n, a, n, a]  [Ba, an, na, an, na]  [Ban, ana, nan, ana]
2  [C, h, e, r, r, y]  [Ch, he, er, rr, ry]  [Che, her, err, rry]

次のようにカラム名のリストを渡すと、既存の DataFrame に名前をつけながら一度に挿入できる。

>>> df[['unigram', 'bigram', 'trigram']] = df.name.apply(multiple_n_gram)
>>> df
     name             unigram                bigram               trigram
0   Apple     [A, p, p, l, e]      [Ap, pp, pl, le]       [App, ppl, ple]
1  Banana  [B, a, n, a, n, a]  [Ba, an, na, an, na]  [Ban, ana, nan, ana]
2  Cherry  [C, h, e, r, r, y]  [Ch, he, er, rr, ry]  [Che, her, err, rry]

もちろん、ふつうに pandas.concat() しても良い。

n-gram の返り値を Series にしてみる

先ほどは各 n-gram を異なるカラムにしただけで、その中の処理が返す値は単なるリストだった。

続いては、試しに n-gram の処理自体が返す値を Series にしてみよう。

>>> def n_gram_series(x, n=1):
...     return pd.Series(x[i : i + n] for i in range(len(x) - n + 1))
... 

くだものの名前は固定長ではないので、返す Series の長さもまちまちになる。 どうなるだろうか。

先ほどと同じように name カラムに適用してみる。

>>> df.name.apply(n_gram_series)
   0  1  2  3  4    5
0  A  p  p  l  e  NaN
1  B  a  n  a  n    a
2  C  h  e  r  r    y

上記から、返すカラムの長さがまちまちなときは、足りない部分に NaN が補完されることがわかる。

いじょう。