データサイエンスコンペ TianChiに挑む！(PyODPSでデータ処理編)

以前TianChiの<strong><a href="https://techblog.sbcloud.co.jp/2018/07/11/tianchi_1/">紹介編</a></strong>と<strong><a href="https://techblog.sbcloud.co.jp/2018/08/01/tianchi_3/">実践編</a></strong>をご紹介させて頂きましたが、今回TianChiシリーズの最終編（データ処理）に関して、まとめていきたいと思います。

<!--more-->

データセットの前処理と言えば、オープンソースのPandasでDataframeとSeriesを操作するのが一般的ですが（TianChiシリーズの実践編でご参照ください）、データのサイズが大きいほど、大量のデータを読み込もうとすると、メモリ不足でエラーになる可能性、もしくは長時間待たされる可能性も高くなります。

Tianchiに開催されるコンペティションは実に様々で、提供されているデータセットが、かならずしもビッグデータの規模とは言えないですが、<span style="text-decoration: underline;"><strong>本<span style="color: #000000; text-decoration: underline;">記事では、ビッグデータの前処理に焦点をあてながら、PyODPSで2次元テーブル、そしてテーブルの列からデータの取り扱い方について、展開させて頂きたいと思います。</span></strong></span>

<span style="font-size: 14pt;"><strong>PyODPSとは？</strong></span>

PyODPS はAlibaba Cloudによって独自に開発されたビッグデータ処理プラットフォームであるMaxComputeのPython SDK です。また、MaxCompute でのビッグデータの前処理と分析を容易にするために、独自のDataFrameフレームワークも提供しています。

Pandasライクなインターフェースや、 DataFrame API をサポートしつつ、 大量のデータをMaxComputeで並列分散処理の機能を活用することができるのが、PyODPSの一番大きなメリットだと言えます。Pandasに既に慣れ親しんだ方にとっては、非常に手をつけやすいツールだと思います。

<strong><span style="font-size: 14pt;">MaxComputeについて</span></strong>

2010年から、Alibaba Cloudが自社の日々増え続ける膨大なデータを格納・処理するために、独自に研究開発した分散コンピューティング処理ベースのデータウェアハウスソリューションです。2014年に自社のビッグデータ系の主力なクラウドサービスとして公開して以来、日本でもお馴染みになった中国11月11日の「独身の日」セールでのビッグデータ処理などに活用されています。

また、従来分散コンピューティングでは、データ分析には専門的な IT 知識が必要になりますが、MaxCompute が提供するデータ連携ソリューションにより、ユーザーは分散コンピューティングの詳細を意識することなくデータ分析を行うことも可能です。
<img class="alignnone size-full wp-image-2757" src="https://techblog.sbcloud.co.jp/assets/2018/07/スクリーンショット-2018-07-19-16.55.03.png" alt="" width="1000" height="400"/>
<span style="font-size: 14pt;"><strong>データのアップロード</strong></span>

MaxComputeへのデータのアップロードに対して、データソースによって、実は複数の方法がありますが、今回はMaxComputeのクライエントであるtunnelを使い、データのアップロード方法を説明します。

具体的に、Alibaba CloudがTianchiで投稿したコンペ『<a href="https://tianchi.aliyun.com/getStart/introduction.htm?raceId=231593&amp;_lang=en_US"><strong>Offline to Online (O2O) Prediction of Coupon Redemption</strong></a>』を例とし、提供されているデータセット(ccf_offline_stage1_train.zip)をまずローカルにダウンロードします。zipファイルを展開すると、ccf_offline_stage1_trainという名前のcsvファイルがあります。

まず、MaxComputeで、データセットに対応するODPSテーブルを作成します。

<img class="alignnone size-full wp-image-2599" src="https://techblog.sbcloud.co.jp/assets/2018/07/13.png" alt="" width="531" height="306" />

次は、以下のtunnel コマンドでデータセットを先ほど述べたODPSテーブルにアップロードします。
<pre class="EnlighterJSRAW" data-enlighter-language="shell">tunnel upload ~/Downloads/dataset/ccf_offline_stage1_train.csv yourProject.ccf_offline_stage1_train;</pre>

<span style="font-size: 14pt;"><strong>データの前処理</strong></span>

まず、Alibaba Cloud アカウント（AccessKey IdとAccessKey Secretがユーザー情報管理でご確認ください。）を使用し、 MaxCompute エントリを初期化します。初期化が完了すると、テーブルを操作できるようになります。
<pre class="EnlighterJSRAW" data-enlighter-language="python">&gt;&gt;&gt;from odps import ODPS

&gt;&gt;&gt;odps = ODPS('accesskey_id', 'accesskey_secret', 
            'project_name',  endpoint = 'endpoint')</pre>
下記のコマンドを呼び出し、テーブルの情報を確認することができます。
<pre class="EnlighterJSRAW" data-enlighter-language="python">&gt;&gt;&gt;t = odps.get_table('ccf_offline_stage1_train')
&gt;&gt;&gt;print(t.schema)

odps.Schema {
 user_id string 
 merchant_id string 
 coupon_id string 
 discount_rate string 
 distance string 
 date_received string 
 date_used string 
}</pre>
DataFrame オブジェクトを作成するには、DataFrameモジュールを入れてから、下記のコマンドを実行します。
<pre class="EnlighterJSRAW" data-enlighter-language="python">&gt;&gt;&gt;from odps.df import DataFrame
&gt;&gt;&gt;dfoff = DataFrame(odps.get_table('ccf_offline_stage1_train'))</pre>
Pandasライクなhead メソッドを使用すると、最初の N 個のデータレコードを取得し、素早くデータをプレビューすることができます。
<pre class="EnlighterJSRAW" data-enlighter-language="python">&gt;&gt;&gt;dfoff.head(10)</pre>

<table style="height: 379px;" width="587">
<tbody>
<tr>
<td style="width: 9.60938px;"><strong> </strong></td>
<td style="width: 63.2188px;"><span style="font-size: 10pt;"><strong>User_id</strong></span></td>
<td style="width: 95.2188px;"><span style="font-size: 10pt;"><strong>Merchant_id</strong></span></td>
<td style="width: 82.2188px;"><span style="font-size: 10pt;"><strong>Coupon_id</strong></span></td>
<td style="width: 107.219px;"><span style="font-size: 10pt;"><strong>Discount_rate</strong></span></td>
<td style="width: 68.2188px;"><span style="font-size: 10pt;"><strong>Distance</strong></span></td>
<td style="width: 109.219px;"><span style="font-size: 10pt;"><strong>Date_received</strong></span></td>
<td style="width: 81.6094px;"><span style="font-size: 10pt;"><strong>Date_used</strong></span></td>
</tr>
<tr>
<td style="width: 9.60938px;"><span style="font-size: 10pt;"><strong>0</strong></span></td>
<td style="width: 63.2188px;"><span style="font-size: 10pt;">1439408</span></td>
<td style="width: 95.2188px;"><span style="font-size: 10pt;">2632</span></td>
<td style="width: 82.2188px;"><span style="font-size: 10pt;">null</span></td>
<td style="width: 107.219px;"><span style="font-size: 10pt;">null</span></td>
<td style="width: 68.2188px;"><span style="font-size: 10pt;">0</span></td>
<td style="width: 109.219px;"><span style="font-size: 10pt;">null</span></td>
<td style="width: 81.6094px;"><span style="font-size: 10pt;">20160217</span></td>
</tr>
<tr>
<td style="width: 9.60938px;"><span style="font-size: 10pt;"><strong>1</strong></span></td>
<td style="width: 63.2188px;"><span style="font-size: 10pt;">1439408</span></td>
<td style="width: 95.2188px;"><span style="font-size: 10pt;">4663</span></td>
<td style="width: 82.2188px;"><span style="font-size: 10pt;">11002</span></td>
<td style="width: 107.219px;"><span style="font-size: 10pt;">150:20:00</span></td>
<td style="width: 68.2188px;"><span style="font-size: 10pt;">1</span></td>
<td style="width: 109.219px;"><span style="font-size: 10pt;">20160528</span></td>
<td style="width: 81.6094px;"><span style="font-size: 10pt;">null</span></td>
</tr>
<tr>
<td style="width: 9.60938px;"><span style="font-size: 10pt;"><strong>2</strong></span></td>
<td style="width: 63.2188px;"><span style="font-size: 10pt;">1439408</span></td>
<td style="width: 95.2188px;"><span style="font-size: 10pt;">2632</span></td>
<td style="width: 82.2188px;"><span style="font-size: 10pt;">8591</span></td>
<td style="width: 107.219px;"><span style="font-size: 10pt;">20:01</span></td>
<td style="width: 68.2188px;"><span style="font-size: 10pt;">0</span></td>
<td style="width: 109.219px;"><span style="font-size: 10pt;">20160217</span></td>
<td style="width: 81.6094px;"><span style="font-size: 10pt;">null</span></td>
</tr>
<tr>
<td style="width: 9.60938px;"><span style="font-size: 10pt;"><strong>3</strong></span></td>
<td style="width: 63.2188px;"><span style="font-size: 10pt;">1439408</span></td>
<td style="width: 95.2188px;"><span style="font-size: 10pt;">2632</span></td>
<td style="width: 82.2188px;"><span style="font-size: 10pt;">1078</span></td>
<td style="width: 107.219px;"><span style="font-size: 10pt;">20:01</span></td>
<td style="width: 68.2188px;"><span style="font-size: 10pt;">0</span></td>
<td style="width: 109.219px;"><span style="font-size: 10pt;">20160319</span></td>
<td style="width: 81.6094px;"><span style="font-size: 10pt;">null</span></td>
</tr>
<tr>
<td style="width: 9.60938px;"><span style="font-size: 10pt;"><strong>4</strong></span></td>
<td style="width: 63.2188px;"><span style="font-size: 10pt;">1439408</span></td>
<td style="width: 95.2188px;"><span style="font-size: 10pt;">2632</span></td>
<td style="width: 82.2188px;"><span style="font-size: 10pt;">8591</span></td>
<td style="width: 107.219px;"><span style="font-size: 10pt;">20:01</span></td>
<td style="width: 68.2188px;"><span style="font-size: 10pt;">0</span></td>
<td style="width: 109.219px;"><span style="font-size: 10pt;">20160613</span></td>
<td style="width: 81.6094px;"><span style="font-size: 10pt;">null</span></td>
</tr>
<tr>
<td style="width: 9.60938px;"><span style="font-size: 10pt;"><strong>5</strong></span></td>
<td style="width: 63.2188px;"><span style="font-size: 10pt;">1439408</span></td>
<td style="width: 95.2188px;"><span style="font-size: 10pt;">2632</span></td>
<td style="width: 82.2188px;"><span style="font-size: 10pt;">null</span></td>
<td style="width: 107.219px;"><span style="font-size: 10pt;">null</span></td>
<td style="width: 68.2188px;"><span style="font-size: 10pt;">0</span></td>
<td style="width: 109.219px;"><span style="font-size: 10pt;">null</span></td>
<td style="width: 81.6094px;"><span style="font-size: 10pt;">20160516</span></td>
</tr>
<tr>
<td style="width: 9.60938px;"><span style="font-size: 10pt;"><strong>6</strong></span></td>
<td style="width: 63.2188px;"><span style="font-size: 10pt;">1439408</span></td>
<td style="width: 95.2188px;"><span style="font-size: 10pt;">2632</span></td>
<td style="width: 82.2188px;"><span style="font-size: 10pt;">8591</span></td>
<td style="width: 107.219px;"><span style="font-size: 10pt;">20:01</span></td>
<td style="width: 68.2188px;"><span style="font-size: 10pt;">0</span></td>
<td style="width: 109.219px;"><span style="font-size: 10pt;">20160516</span></td>
<td style="width: 81.6094px;"><span style="font-size: 10pt;">20160613</span></td>
</tr>
<tr>
<td style="width: 9.60938px;"><span style="font-size: 10pt;"><strong>7</strong></span></td>
<td style="width: 63.2188px;"><span style="font-size: 10pt;">1832624</span></td>
<td style="width: 95.2188px;"><span style="font-size: 10pt;">3381</span></td>
<td style="width: 82.2188px;"><span style="font-size: 10pt;">7610</span></td>
<td style="width: 107.219px;"><span style="font-size: 10pt;">200:20:00</span></td>
<td style="width: 68.2188px;"><span style="font-size: 10pt;">0</span></td>
<td style="width: 109.219px;"><span style="font-size: 10pt;">20160429</span></td>
<td style="width: 81.6094px;"><span style="font-size: 10pt;">null</span></td>
</tr>
<tr>
<td style="width: 9.60938px;"><span style="font-size: 10pt;"><strong>8</strong></span></td>
<td style="width: 63.2188px;"><span style="font-size: 10pt;">2029232</span></td>
<td style="width: 95.2188px;"><span style="font-size: 10pt;">3381</span></td>
<td style="width: 82.2188px;"><span style="font-size: 10pt;">11951</span></td>
<td style="width: 107.219px;"><span style="font-size: 10pt;">200:20:00</span></td>
<td style="width: 68.2188px;"><span style="font-size: 10pt;">1</span></td>
<td style="width: 109.219px;"><span style="font-size: 10pt;">20160129</span></td>
<td style="width: 81.6094px;"><span style="font-size: 10pt;">null</span></td>
</tr>
<tr>
<td style="width: 9.60938px;"><span style="font-size: 10pt;"><strong>9</strong></span></td>
<td style="width: 63.2188px;"><span style="font-size: 10pt;">2029232</span></td>
<td style="width: 95.2188px;"><span style="font-size: 10pt;">450</span></td>
<td style="width: 82.2188px;"><span style="font-size: 10pt;">1532</span></td>
<td style="width: 107.219px;"><span style="font-size: 10pt;">30:05:00</span></td>
<td style="width: 68.2188px;"><span style="font-size: 10pt;">0</span></td>
<td style="width: 109.219px;"><span style="font-size: 10pt;">20160530</span></td>
<td style="width: 81.6094px;"><span style="font-size: 10pt;">null</span></td>
</tr>
</tbody>
</table>
Pandasの基本的なデータ構造（DataFrame と Series）と同様に、PyODPSのDataFrameフレームワークにも、Collectionと呼ばれる二次元のテーブルとSequenceと呼ばれる一次元の配列があります。ただここに気をつけて欲しいのは、Seriesは二次元テーブルの列(column)か行(row)を指すのに対して、Sequenceは二次元テーブルの列のみと指しますので、行をSequenceとしてデータを取り出すことができません。

<table style="width: 577px;">
<tbody>
<tr>
<td style="width: 230px;">Operation</td>
<td style="width: 152px;">Pandas</td>
<td style="width: 167px;">PyODPS</td>
</tr>
<tr>
<td style="width: 230px;">Select column</td>
<td style="width: 152px;">df[col]</td>
<td style="width: 167px;">df[col] , df.col</td>
</tr>
<tr>
<td style="width: 230px;">Select row by label</td>
<td style="width: 152px;">df.loc[label]</td>
<td style="width: 167px;">×</td>
</tr>
<tr>
<td style="width: 230px;">Select row by integer location</td>
<td style="width: 152px;">df.iloc[loc]</td>
<td style="width: 167px;">×</td>
</tr>
</tbody>
</table>
PyOPDS Dataframに１つの便利な機能としては、to_pandas()関数を活用することによって、PyODPSで処理した結果をPandas Dataframeの形式で保存することができます。また、MatplotlibやSeabornなどのライブラリを一緒に使うと、描画の機能も実現することができます。
<pre class="EnlighterJSRAW" data-enlighter-language="python">import matplotlib.pyplot as plt
import seaborn as sns

sns.set_style('darkgrid')
sns.set_context("notebook", font_scale= 1.4)
x = dfoff.distance.unique().to_pandas().values
y = dfoff.groupby('distance').agg(count=dfoff.distance.count()).to_pandas()
plt.yscale('log')
plt.ylabel('Distance Count')
plt.bar(x,y['count'].values)
plt.tight_layout()</pre>
<img class="alignnone size-full wp-image-2736" src="https://techblog.sbcloud.co.jp/assets/2018/07/Tianchi_2.png" alt="" width="432" height="288" />

<hr/>
<span style="font-size: 14pt;"><strong>参考サイト：</strong></span>

本記事に関連するMaxCompute、PyODPSのオープンソースやTunnelに関しての詳しい設定方法は以下リンクをご参照ください。

<ul style="list-style-type: disc;">
<li><a href="https://jp.alibabacloud.com/help/product/27797.htm">MaxComputeの公式ドキュメント</a></li><li><a href="https://jp.alibabacloud.com/help/doc-detail/27833.htm?spm=a21mg.p38356.b99.38.13a5a7fbdNwRZi">Tunnelの使い方</a></li>
<li><a href="https://github.com/aliyun/aliyun-odps-python-sdk?spm=a21mg.p38356.a3.1.7d601812SKHhD8">PyODPSのGitHub</a></li>
</ul>