ログデータの省メモリなmatrix変換

やりたいこと

以下のような、user, item などキーとして階層的に値を持つログデータがあるとして、 (以下、user item はケースによって読み替えてください)

>>> df = pd.DataFrame([['user1','item1',5],['user1','item2',4],['user2','item2',5],['user2','item3',6],['user3','item4',3]], columns=['username','itemname','rate'])
>>> df
  username itemname  rate
0    user1    item1     5
1    user1    item2     4
2    user2    item2     5
3    user2    item3     6
4    user3    item4     3

これを matrix 形式に変換したい場合、pandas では以下のように書けます。

>>> df.groupby(['itemname', 'username']).mean().unstack(fill_value=0).values
array([[5, 0, 0],
       [4, 5, 0],
       [0, 6, 0],
       [0, 0, 3]])

※ user×item の重複がある場合は評価値の平均をとる

問題

この処理を上記のように pandas で行う場合、対象となる全データを DataFrame として保持する必要があり、大規模なデータである場合は処理自体がメモリエラーに繋がります。 また、データ自体が既にメモリに乗らない場合、データをメモリに乗る形で分割して読み込みその都度前処理を行う必要があるがそれもできないという問題があります。

そのため、上記の変換処理を pandas 以外で行いたいというのが今回のモチベーションとなります。

方法

各キーをインデックスに変換し、各値とそれらインデックスをリストとして保持すればいいっていう話でした。

インデックス変換

以下のようにキーとなる各値を Label Encoding で変換します。

>>> from sklearn.preprocessing import LabelEncoder
>>> le_username = LabelEncoder()
>>> le_itemname = LabelEncoder()
>>> df['username'] = le_username.fit_transform(df['username'])
>>> df['itemname'] = le_username.fit_transform(df['itemname'])
>>> df
   username  itemname  rate
0         0         0     5
1         0         1     4
2         1         1     5
3         1         2     6
4         2         3     3

リスト化

何番目のユーザーの何番目のアイテムがどのような値を持つかという形でデータを保持するため、以下のようにリスト形式で各値を取り出します。

>>> row = df['itemname'].values.tolist()
>>> col = df['username'].values.tolist()
>>> value = df['rate'].values.tolist()
>>> row
[0, 1, 1, 2, 3]
>>> col
[0, 0, 1, 1, 2]
>>> value
[5, 4, 5, 6, 3]

データを分割して読み込んで行く場合、ここでリストを extend していきます。

matrix へ変換

上記リストを、sparse matrix へ変換します。

>>> from scipy.sparse import coo_matrix
>>> matrix = coo_matrix((value, (row, col)))
>>> matrix
<4x3 sparse matrix of type '<class 'numpy.int64'>' with 5 stored elements in COOrdinate format>

dense にすると以下のようになり、求めていた matrix が取得できました。

>>> matrix.toarray()
array([[5, 0, 0],
       [4, 5, 0],
       [0, 6, 0],
       [0, 0, 3]])