pandasを使ってイベントデータを１ユーザ１行の形式に集計する


はじめに
========

今回「イベントデータ」という言葉を **(時刻, イベント名 (, ユーザ)(, 数量))** のようなデータ の意味で使います。
ユーザの予測モデルなどを作ろうとした時に、このようなイベントデータを「１ユーザ１行」になるように整形したくなることがあります。

pandas でこの辺を処理を行う方法についてメモしておきます。

方法
=======

まず、下記のようなイベントデータを例に考えます。


```python
import pandas as pd

data = [
    ['user1', '2015-7-1 10:00:00', 'login'],
    ['user1', '2015-7-1 10:00:10', 'item'],
    ['user1', '2015-7-1 10:00:30', 'item'],
    ['user2', '2015-7-1 10:01:00', 'top'],
    ['user1', '2015-7-1 10:01:30', 'cart'],
    ['user2', '2015-7-1 10:01:50', 'login'],
    ['user2', '2015-7-1 10:02:30', 'logout'],

    ['user1', '2015-7-2 13:00:00', 'login'],
    ['user1', '2015-7-2 13:01:00', 'logout'],
    ['user3', '2015-7-2 13:01:00', 'top'],
    ['user2', '2015-7-2 13:01:50', 'login'],
    ['user2', '2015-7-2 13:02:30', 'item'],
    ['user2', '2015-7-2 13:03:30', 'cart'],
    ['user2', '2015-7-2 13:03:30', 'history'],
]
df = pd.DataFrame(data)
df.columns = ['user_id', 'time', 'event']
df['time'] = pd.to_datetime(df['time'])
df['dummy'] = 1
df
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>user_id</th>
      <th>time</th>
      <th>event</th>
      <th>dummy</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>user1</td>
      <td>2015-07-01 10:00:00</td>
      <td>login</td>
      <td>1</td>
    </tr>
    <tr>
      <th>1</th>
      <td>user1</td>
      <td>2015-07-01 10:00:10</td>
      <td>item</td>
      <td>1</td>
    </tr>
    <tr>
      <th>2</th>
      <td>user1</td>
      <td>2015-07-01 10:00:30</td>
      <td>item</td>
      <td>1</td>
    </tr>
    <tr>
      <th>3</th>
      <td>user2</td>
      <td>2015-07-01 10:01:00</td>
      <td>top</td>
      <td>1</td>
    </tr>
    <tr>
      <th>4</th>
      <td>user1</td>
      <td>2015-07-01 10:01:30</td>
      <td>cart</td>
      <td>1</td>
    </tr>
    <tr>
      <th>5</th>
      <td>user2</td>
      <td>2015-07-01 10:01:50</td>
      <td>login</td>
      <td>1</td>
    </tr>
    <tr>
      <th>6</th>
      <td>user2</td>
      <td>2015-07-01 10:02:30</td>
      <td>logout</td>
      <td>1</td>
    </tr>
    <tr>
      <th>7</th>
      <td>user1</td>
      <td>2015-07-02 13:00:00</td>
      <td>login</td>
      <td>1</td>
    </tr>
    <tr>
      <th>8</th>
      <td>user1</td>
      <td>2015-07-02 13:01:00</td>
      <td>logout</td>
      <td>1</td>
    </tr>
    <tr>
      <th>9</th>
      <td>user3</td>
      <td>2015-07-02 13:01:00</td>
      <td>top</td>
      <td>1</td>
    </tr>
    <tr>
      <th>10</th>
      <td>user2</td>
      <td>2015-07-02 13:01:50</td>
      <td>login</td>
      <td>1</td>
    </tr>
    <tr>
      <th>11</th>
      <td>user2</td>
      <td>2015-07-02 13:02:30</td>
      <td>item</td>
      <td>1</td>
    </tr>
    <tr>
      <th>12</th>
      <td>user2</td>
      <td>2015-07-02 13:03:30</td>
      <td>cart</td>
      <td>1</td>
    </tr>
    <tr>
      <th>13</th>
      <td>user2</td>
      <td>2015-07-02 13:03:30</td>
      <td>history</td>
      <td>1</td>
    </tr>
  </tbody>
</table>
</div>

このデータを 「１ユーザ１行」として「日毎イベント毎の発生回数の総和」を列とするように整形したいとします。


pivot_table と Grouper
---------------------
DataFrameクラスの `pivot_table()` を使うと Excelのピボットテーブルと似たような操作を行うことができます。
日毎に集計したいのですが、 `Grouper()` というのを使うとdatetime型の列に対してよしなにGroup化できます。なんて便利な・・。

```python
res2 = df.pivot_table(index=['user_id', pd.Grouper(freq='d', key='time')], columns='event', values='dummy', aggfunc=len)
res2
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>event</th>
      <th>cart</th>
      <th>history</th>
      <th>item</th>
      <th>login</th>
      <th>logout</th>
      <th>top</th>
    </tr>
    <tr>
      <th>user_id</th>
      <th>time</th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th rowspan="2" valign="top">user1</th>
      <th>2015-07-01</th>
      <td>1</td>
      <td>NaN</td>
      <td>2</td>
      <td>1</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>2015-07-02</th>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>1</td>
      <td>1</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th rowspan="2" valign="top">user2</th>
      <th>2015-07-01</th>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>1</td>
      <td>1</td>
      <td>1</td>
    </tr>
    <tr>
      <th>2015-07-02</th>
      <td>1</td>
      <td>1</td>
      <td>1</td>
      <td>1</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>user3</th>
      <th>2015-07-02</th>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>1</td>
    </tr>
  </tbody>
</table>
</div>


Grouperの `freq='d'` なので１日単位での集計になります。 `freq='M'` とかにすると月単位などになります。


日付を列にもってくる
-----------------
unstack() を使うと良いです。

```python
res3 = res2.unstack()
res3
```


<div>
<table border="1" class="dataframe">
  <thead>
    <tr>
      <th>event</th>
      <th colspan="2" halign="left">cart</th>
      <th colspan="2" halign="left">history</th>
      <th colspan="2" halign="left">item</th>
      <th colspan="2" halign="left">login</th>
      <th colspan="2" halign="left">logout</th>
      <th colspan="2" halign="left">top</th>
    </tr>
    <tr>
      <th>time</th>
      <th>2015-07-01</th>
      <th>2015-07-02</th>
      <th>2015-07-01</th>
      <th>2015-07-02</th>
      <th>2015-07-01</th>
      <th>2015-07-02</th>
      <th>2015-07-01</th>
      <th>2015-07-02</th>
      <th>2015-07-01</th>
      <th>2015-07-02</th>
      <th>2015-07-01</th>
      <th>2015-07-02</th>
    </tr>
    <tr>
      <th>user_id</th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>user1</th>
      <td>1</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>2</td>
      <td>NaN</td>
      <td>1</td>
      <td>1</td>
      <td>NaN</td>
      <td>1</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>user2</th>
      <td>NaN</td>
      <td>1</td>
      <td>NaN</td>
      <td>1</td>
      <td>NaN</td>
      <td>1</td>
      <td>1</td>
      <td>1</td>
      <td>1</td>
      <td>NaN</td>
      <td>1</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>user3</th>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>1</td>
    </tr>
  </tbody>
</table>
</div>

日付を上にもってきたい
---------------
`swaplevel(入れ替えたいIndex1, 入れ替えたいIndex2)` を使います。
あと、列(軸番号1)について操作したいので `axis=1` を付けます。

```python
res4 = res3.swaplevel(0, 1, axis=1)
res4
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr>
      <th>time</th>
      <th>2015-07-01</th>
      <th>2015-07-02</th>
      <th>2015-07-01</th>
      <th>2015-07-02</th>
      <th>2015-07-01</th>
      <th>2015-07-02</th>
      <th>2015-07-01</th>
      <th>2015-07-02</th>
      <th>2015-07-01</th>
      <th>2015-07-02</th>
      <th>2015-07-01</th>
      <th>2015-07-02</th>
    </tr>
    <tr>
      <th>event</th>
      <th>cart</th>
      <th>cart</th>
      <th>history</th>
      <th>history</th>
      <th>item</th>
      <th>item</th>
      <th>login</th>
      <th>login</th>
      <th>logout</th>
      <th>logout</th>
      <th>top</th>
      <th>top</th>
    </tr>
    <tr>
      <th>user_id</th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>user1</th>
      <td>1</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>2</td>
      <td>NaN</td>
      <td>1</td>
      <td>1</td>
      <td>NaN</td>
      <td>1</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>user2</th>
      <td>NaN</td>
      <td>1</td>
      <td>NaN</td>
      <td>1</td>
      <td>NaN</td>
      <td>1</td>
      <td>1</td>
      <td>1</td>
      <td>1</td>
      <td>NaN</td>
      <td>1</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>user3</th>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>1</td>
    </tr>
  </tbody>
</table>
</div>

列は日付順にならんで欲しい
--------
`sortlevel()` を使います。

```python
res4.sortlevel(0, axis=1)
```


<div>
<table border="1" class="dataframe">
  <thead>
    <tr>
      <th>time</th>
      <th colspan="6" halign="left">2015-07-01</th>
      <th colspan="6" halign="left">2015-07-02</th>
    </tr>
    <tr>
      <th>event</th>
      <th>cart</th>
      <th>history</th>
      <th>item</th>
      <th>login</th>
      <th>logout</th>
      <th>top</th>
      <th>cart</th>
      <th>history</th>
      <th>item</th>
      <th>login</th>
      <th>logout</th>
      <th>top</th>
    </tr>
    <tr>
      <th>user_id</th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>user1</th>
      <td>1</td>
      <td>NaN</td>
      <td>2</td>
      <td>1</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>1</td>
      <td>1</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>user2</th>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>1</td>
      <td>1</td>
      <td>1</td>
      <td>1</td>
      <td>1</td>
      <td>1</td>
      <td>1</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>user3</th>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>1</td>
    </tr>
  </tbody>
</table>
</div>


これで目的の形に整形できました。

さいごに
=========

「Eventの種類数×日付数」の数の列ができてしまうので、それが多すぎる場合は使えないですね。
Event種類数が多くないか、月単位で集計するという場合には使えると思います。

それにしてもpandas便利ですねー