akiyoko blog

akiyoko の IT技術系ブログです

Pandas の DataFrame の基本的な使い方

Python でデータ分析をするためのライブラリといえば「Pandas」がデファクトですが、今回は、Pandas の DataFrame の基本的な使い方をまとめてみようと思い立ちました。

特に、DataFrame で時系列データを扱うことを想定しています。具体的には、「金融データのPythonでの扱い方 - 今日も窓辺でプログラム」のように株価データを扱ってみたいと考えています。

 

前提条件

ローカルの実行環境は以下の通りです。

  • MacOS Sierra 10.12.3
  • Python 2.7.12 (Anaconda 4.2.0)
  • Jupyter Notebook 4.2.0
  • pandas 0.18.1


参考までに、MacOS Sierra に(Pandas や Jupyter Notebook などのデータ分析関連の Python パッケージが全部入った)Anaconda を Pyenv を使ってインストールする手順はこちらになります。

<過去記事>
akiyoko.hatenablog.jp



 

pandas.DataFrame の作成方法

pandas.DataFrame の作成方法は、大きく分けて3つあります。


 

1)二次元配列を引数にする

二次元配列の一次元目は縦方向、二次元目は横方向に並びます。

import pandas as pd

df = pd.DataFrame([[1, 11, 111], [2, 22, 222], [3, 33, 333], [4, 44, 444]])
df

横方向のデータを縦に繋いだイメージです。伝わりますかね??

f:id:akiyoko:20170325004558p:plain:w100


DataFrame を縦方向に連結する

DataFrame を縦方向に連結するには、pandas.concat() あるいは pandas.DataFrame.append() を使います。

df = pd.DataFrame([[1, 11, 111]])
df2 = pd.DataFrame([[2, 22, 222]])
df3 = pd.DataFrame([[3, 33, 333]])
df4 = pd.DataFrame([[4, 44, 444]])
df = pd.concat([df, df2, df3, df4])

# append でもOK
# df = df.append(df2).append(df3).append(df4)
df

f:id:akiyoko:20170325005953p:plain:w100

しかしながら、append は内部的に全体のコピーが毎回走るのでループで一行ずつ追加するような処理は避けた方がよいそうです。

(参考)Python pandas 図でみる データ連結 / 結合処理 - StatsFragments


 

index や columns を指定する

index(縦方向のラベル) や columns(横方向のラベル)を指定することが可能です。

df = pd.DataFrame([[1, 11, 111], [2, 22, 222], [3, 33, 333], [4, 44, 444]], index=['one', 'two', 'three', 'four'], columns=['A', 'B', 'C'])
df

f:id:akiyoko:20170325010057p:plain:w120


時系列データを扱う

時系列っぽいデータを作成してみます。

df = pd.DataFrame([[18818.580078, 136000, 18450.980469],
                   [18398.759766, 128300, 18374.00],
                   [18410.570312, 142200, 18191.320312],
                   [18139.769531, 163000, 17767.339844]],
                  index=pd.to_datetime(['2016-01-04', '2016-01-05', '2016-01-06', '2016-01-07']),
                  columns=['Open', 'Volume', 'Adj Close'])
df

f:id:akiyoko:20170325010155p:plain:w300


2)辞書を引数にする

辞書を引数にして pandas.DataFrame を作成することもできます。
なお辞書の値は、リストまたは pandas.Series を指定します。

df = pd.DataFrame({'A': [1, 11, 111],
                   'B': pd.Series([2, 22, 222]),
                   'C': pd.Series({0: 3, 1: 33, 2: 333})})
df

1)とは逆に、縦方向のデータを横に繋いだイメージになります。

f:id:akiyoko:20170325010246p:plain:w120

columns の値が辞書ごとに指定できるので便利ですが、時系列データを扱うことを考えると、カラムごとの値のリストをそれぞれ用意する必要があるのでちょっと使いづらいですかね。。



ちなみに、リストの要素数が他と合わないと ValueError が発生してしまいます。

df = pd.DataFrame({'A': [1, 11],
                   'B': pd.Series([2, 22, 222]),
                   'C': pd.Series({0: 3, 1: 33, 2: 333})})
ValueError: array length 2 does not match index length 3


なお Series の場合は、足りない要素が「NaN」で補完されるので便利です。

df = pd.DataFrame({'A': [1, 11, 111],
                   'B': pd.Series([2, 22]),
                   'C': pd.Series({0: 3, 1: 33, 2: 333})})
df

f:id:akiyoko:20170325110441p:plain:w120

 

DataFrame を横方向に連結する

なお、DataFrameを横方向に結合するには、pandas.concat() あるいは pandas.DataFrame.join() を使います。

df = pd.DataFrame({'A': [1, 11, 111]})
df2 = pd.DataFrame({'B': [2, 22, 222]})
# 横方向に結合するときは axis=1 を指定
df = pd.concat([df, df2], axis=1)
# あるいは、index が同じであれば、index をキーにして結合する DataFrame.join を使ってもよい
# df = df.join(df2)
df

f:id:akiyoko:20170325010345p:plain:w80


 

時系列データを扱う

2)の作成方法では時系列データを扱いづらいのですが、もしやるなら一旦以下のように DataFrame を作成して、作成した後に DataFrame.T で縦横を反転させる方がいいのかな。

df = pd.DataFrame({'2016-01-04': [18818.580078, 136000, 18450.980469],
                   '2016-01-05': [18398.759766, 128300, 18374.00],
                   '2016-01-06': [18410.570312, 142200, 18191.320312],
                   '2016-01-07': [18139.769531, 163000, 17767.339844]}, index=['Open', 'Volume', 'Adj Close'])
df = df.T
df

f:id:akiyoko:20170325010408p:plain:w300


うーん。やっぱり使いづらいですね。。



 

3)pandas.read_csv を使う

ここからが本題。

pandas.read_csv() を使えば、CSVファイルから簡単に DataFrame を作成することができます。

日経平均のデータ(n225.csv)を読み込んでみます。データは Nikkei 225 Stock - Yahoo Finance からダウンロードしました。

df = pd.read_csv('n225.csv', index_col='Date', parse_dates=True, usecols=['Date', 'Open', 'Volume', 'Adj Close'])
df = df.sort_index()
df.head(10)

f:id:akiyoko:20170326012255p:plain:w300


金融データのPythonでの扱い方 - 今日も窓辺でプログラム」では、以下のように DataFrame を作成していました。

# 2016年1年分の DataFrame を用意
df = pd.DataFrame(index=pd.date_range('2016-01-01', '2016-12-31'))

# 日経平均のデータを読み込んで join
df = df.join(pd.read_csv('n225.csv', index_col='Date', parse_dates=True, usecols=['Date', 'Open', 'Volume', 'Adj Close']))

# 市場が休みの日のデータを取り除く
df = df.dropna()
df.head(10)

f:id:akiyoko:20170326012353p:plain:w300


最初のやり方と結果が変わらないような気がするのですが、まあそれは追々考えることにしましょう。



 

余談:Iris のデータを Pandas で使う方法

統計分析の練習用サンプルデータと言ったらやっぱり Iris でしょ、ということでデータをロードしようと思ったのですが、pandas には標準で入っていないようです。

Anaconda には標準で scikit-learn が入っているので、そちらを使えば簡単にロードできるようです。
(参考)irisのデータセットをpandasで使う - DISTRICT 37

import pandas as pd
from sklearn import datasets

iris = datasets.load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['Species'] = iris.target_names[iris.target]
df.rename(columns={'sepal length (cm)': 'Sepal.Length',
                   'sepal width (cm)': 'Sepal.Width',
                   'petal length (cm)': 'Petal.Length',
                   'petal width (cm)': 'Petal.Width'},
          inplace=True)
df.head()

f:id:akiyoko:20170331230004p:plain:w450




なお、rpy2(R interface)パッケージを使ってもいいようですが、rpy2 は Anaconda 4.2.0 にはデフォルトで入っていなかったので見送りました。

(参考)Sample Datasets in Pandas - Stack Overflow

import pandas.rpy.common as rcom
iris = rcom.load_data('iris')
ImportError: No module named rpy2.robjects.packages

 

まとめ

pandas.DataFrame で時系列データを作成するには、pandas.read_csv を使うのが圧倒的に便利です。もちろん、対象データが CSV形式で保存されているなら、という条件付きですが。

pandas.read_csv には把握するのが大変なほど多種多様なオプション引数が用意されており、使いこなせれば強力な武器になりそうです。

f:id:akiyoko:20170325121734p:plain

pandas.read_csv — pandas 0.21.1 documentation より)


なお今回のソースコードは、GitHub のリポジトリにアップしておきました。

github.com




 

「実践!機械学習 - Web系企業 CTO が実例を公開」に参加してきました

主催

Forkwell Jobs

会場

株式会社フロムスクラッチ
東京都新宿区西新宿7丁目20番1号(住友不動産西新宿ビル17階)


 

全体の感想など

最近よくある機械学習の基礎知識系の勉強会ではなく、機械学習をすでにビジネスに導入・応用して実際に運用している企業のCTOが集結して、その知見を披露してもらえるというイベントでした。

とは言え、私も機械学習に詳しいわけではなく、最近ようやく、

www.udemy.com

を修了したばかりの初心者なので全く付いていけないところも何度かありましたが、とりあえずキーワードをメモしておいて、少し勉強してからまた見直してみたいと思います。




マーケティングプラットフォームにおけるレコメンドサービス

井戸端 洋彰 氏(株式会社フロムスクラッチ)


広告データや広告ページのアクセスログおよびビジネスデータを収集・統合して分析・活用するためのプラットフォーム「B→Dash」の運用実績が二年半ほどあるとのことです。

中でもデータの統合が一番大変で、機械学習については、どのアルゴリズムを使うかよりも、マーケティングの設計の方が大事。結果を分析して得られた「示唆」を抽出し、次の施策へ活かすプロセスが重要とのことでした。


<関連サイト>
seleck.cc



 

トラッキングデータを使った機械学習活用

柴山 直樹 氏(株式会社プレイド)


サイト訪問者行動解析プラットフォーム「KARTE(カルテ)」に、「強化学習による配信最適化」や「回帰/2クラス分類によるユーザモデリング」という形で機械学習を導入しているとのこと。

導入ポリシーとしては、「人間の発想を活かす」「機械学習はそれを補助するように使う」ということで、機械学習が導き出す答えが定型化して退屈なものになってしまわないように気を付けているとのことでした。アルゴリズムよりもサービスの本質に目を向けないとダメで、事業のコアにするのは危険?(マーケティングの手段としてはアリ)という話も。また、世間の AIへの期待が高すぎるということを危惧されているようでした。


<キーワード>



<関連?スライド>

 

パネルディスカッション

  • 機械学習導入の難易度は、それぞれのドメインにおけるデータの集めやすさやデータの質に依る
    • 前処理が大変なので、データが汚いとそれだけ大変になる
  • GV(旧 Google Ventures)や Google が提唱している「デザインスプリント」の本を元に、組織でビジネスアイデアを検討している

  • 今機械学習ができるのは結局、分類と回帰のみ。近い将来、汎用AIを作るフェーズになっていく

ゼロからはじめる Amazon QuickSight(AWS でお手軽データ分析 その3/3)

前々回の記事 および 前回の記事 で、Scrapy で Webスクレイピングしたデータを CSVファイルとして S3 に格納し、Amazon Athena のテーブルを作成して CSV のデータを流し込むところまでを実施しました。

今回は、作成した Amazon Athena のテーブルをデータソースとして Amazon QuickSight のデータ分析用のストアに取り込み、ダッシュボードからグラフを作成してみたいと思います。


<過去記事>
akiyoko.hatenablog.jp

akiyoko.hatenablog.jp


 

Amazon QuickSight とは

Amazon QuickSight については、昨年末の AWS Black Belt Online Seminar の資料に詳しく説明がされています。


要約すると、Amazon QuickSight は

  • すぐに利用可能(セットアップ不要)
  • フルマネージド
  • 多様なデータソースが使える
  • 低コスト
  • 結果のグラフはスマホにも最適化

という特徴を持つクラウドベースの BI ツールです。Redshift、RDS、Aurora、Athena、S3 などを始めとする AWS 上のデータセットをデータソースとして取り込んで、ダッシュボード上で様々なグラフ(*1)でデータを可視化することができます。CSVファイルや Excelファイルを QuickSight に直接アップロードしたり、オンプレミスサーバ上のデータベースをデータソースとして利用することもできるようですが、AWS のプロダクトと連携して利用するのがメインのユースケースとなるでしょう。

なお現時点では、N. Virginia、Oregon、Ireland の 3リージョンでしか利用できないようです。

「SPICE」というキーワードがちょくちょく出てきますが、独自に開発したインメモリデータストア用の超高速エンジンで、「Super-fast, Parallel, In-Memory Calculation Engine」の略だそうです。使用料は、その SPICEの容量(最初の1GBは無料)とユーザ数(最初の1ユーザは無料)に対して課金されます。お試しで使うのであれば、無料の範囲で十分使えそうです。

(参考)Amazon QuickSight | Editions


概要レベルの資料では、
Amazon QuickSight:【概要】Amazon QuickSightとは何か? | Developers.IO
も有用です。

あと、全然読み切れてませんが、クラスメソッドの しんや 氏の「Amazon QuickSight (全部俺) Advent Calendar 2016 - Qiita」なんていう資料もありました。猛者です。




 

目的

「AWS でお手軽データ分析」の全体像は、下図のように Scrapy → Amazon S3 → Amazon Athena → Amazon QuickSight という流れで AWS のいろいろなサービスを使ってデータ分析をすることを想定していますが、最終回である今回は、作成した Amazon Athena のテーブルを Amazon QuickSight のデータ分析用のストアに読み込んで可視化するところを試してみます。

f:id:akiyoko:20170311224916p:plain



 

QuickSight を使ってみる

QuickSight アカウントの作成

AWS Management Consle のアカウントとは別に、QuickSight アカウントを作成する必要があります。

QuickSight のアカウントについては「Amazon QuickSight: ユーザー管理やパーミッション設定について | Developers.IO」が詳しいです。


AWS Management Console にログインし、サービス一覧から「QuickSight」を選択します。
f:id:akiyoko:20170312115142p:plain

https://quicksight.aws.amazon.com/
にリダイレクトされますが、まだサインアップしていないので入れません。
ここでログアウトせずに続けて「Sign up」することで、現在 AWS Management Console にログインしているアカウントを QuickSight アカウントとして紐付けることができるようです。
f:id:akiyoko:20170312115205p:plain

「Standard edition」を選択します。最初の1ユーザーは無料です。
f:id:akiyoko:20170312115223p:plain

以下の内容を設定します。

QuickSight account name 任意(全体でユニークになるように)
Notification email address 任意
QuickSight capacity region US East (N.Virginia)


またここで、QuickSight からアクセスするリソースに対してパーミッションを与える必要があります。 *2

今回の例では、

  • Amazon Athena
  • Amazon S3(Athena のテーブルが参照している S3 バケット)

を許可する必要があります。

ということで、「Choose S3 buckets」をクリックします。
f:id:akiyoko:20170312115242p:plain

Athena のテーブルが参照している S3 バケット(今回の例では「marketstat」)を指定します。
f:id:akiyoko:20170312115306p:plain

アカウント作成を完了します。
f:id:akiyoko:20170312115324p:plain

f:id:akiyoko:20170312115347p:plain

グラフ(Visual)の作成

QuickSight のトップ画面はこんな感じです。

「New Analysis」をクリック。
f:id:akiyoko:20170312115409p:plain

「New data set」をクリックします。
f:id:akiyoko:20170312115429p:plain

取り込むデータセットとして、「Athena」を選択します。
f:id:akiyoko:20170312115505p:plain

データソース名を任意に設定します。
f:id:akiyoko:20170312115540p:plain

データを取り込む対象となる Athena のデータベースとテーブルを選択します。
f:id:akiyoko:20170312115611p:plain

インポート先に「SPICE」を選択して、「Visualize」をクリックします。
f:id:akiyoko:20170312115647p:plain

データの取り込みが完了すると、「Import complete」と表示されます。
f:id:akiyoko:20170312115748p:plain

ここから、グラフを作成していきます。
まず、グラフのタイプを選んで、X軸とY軸を選択します。
f:id:akiyoko:20170312115822p:plain

X軸をソートするには、以下のように操作します。
f:id:akiyoko:20170312115859p:plain

次に、フィルタ(データの抽出条件)を設定します。
f:id:akiyoko:20170312115934p:plain

左上の「Add」をクリックすると、グラフを「Visual」として保存することができます。
f:id:akiyoko:20170312120048p:plain


また、複数のグラフをダッシュボードとして保存して他のユーザと共有することができるほか、複数のグラフをプレゼン資料のようにまとめたものを「Story」として保存することもできます。



  

まとめ

今回、Scrapy → Amazon S3 → Amazon Athena → Amazon QuickSight という流れで AWS のいろいろなサービスを使って Webスクレイピングしたデータを可視化して分析するまでを、「AWS でお手軽データ分析」と称して三回に分けて記事にまとめてみました。

f:id:akiyoko:20170311224916p:plain


途中何度か軽く躓きかけたものの、全体的にはすんなりとクラウドベースでデータ分析する入り口までたどり着くことができました。
今後は、スクレイピングしたデータやダウンロードした公式のデータを使って、いろいろな分析を実際にしていきたいと思います。

*1:利用できるグラフの種類については、「利用可能な表示形式一覧 #quicksight #01 | Amazon QuickSight Advent Calendar 2016 | Developers.IO」を参照

*2:ここでパーミッションを設定しなくても、後で[Manage QuickSight] > [Account settings]から設定を変更することもできます。

ゼロからはじめる Amazon Athena(AWS でお手軽データ分析 その2/3)

前回の記事 で、Scrapy で Webスクレイピングしたデータを CSV形式で S3 に格納しました。

今回は、S3 に格納した CSVファイルに対して、Amazon Athena を使ってデータ分析用のテーブルに取り込みたいと思います。


<過去記事>
akiyoko.hatenablog.jp



Amazon Athena とは

Amazon Athena については、先日開催された AWS Black Belt Online Seminar の「Amazon Athena」回の資料が詳しいです。


Athena は OLAP(OnLine Analytical Processing)ツールとして簡単な分析向けには使えるが、ETL(Extract, Transform, Load)用途としては向かないとのこと。ただし、大規模でないデータに対して、低頻度で ETL 処理をするユースケースには使えるとのことです。

  • 高速な分散クエリエンジン「Presto」を使用
    • インメモリで処理するので、バッチ処理ではなくてインタラクティブ向け
  • データをフルスキャン&変換する用途で使うと高コストになるので、バッチ処理には向かない
    • 大規模データに対してフルスキャンを定期的に行いたいなら、EMR を使う
    • サブクエリや JOIN を駆使した複雑な主計や高頻度なレポーティングをしたいなら、Redshift を使う

アクセス手段は Amazon Management Console、JDBLドライバ経由のみ。API や CLI は未提供とのことです。JDBC 経由で直接クエリを投げられるので、自前の BIツールとかも使えるようですね。

料金はクエリ単位の従量課金で、S3 のデータスキャンに対して「$5/1TB」で課金されます。注意点としては、

  • リージョンをまたぐ場合は、データ転送時間と転送料金が掛かる
  • パーティションをうまく設定するとスキャンするデータ量を効果的に減らすことができる

などが挙げられていました。

なお、Athena はディレクトリを指定して検索するので、バケット直下に直接ファイルを置くのは間違いとのことです。

 

目的

「AWS でお手軽データ分析」の全体像は、下図のように Scrapy → Amazon S3 → Amazon Athena → Amazon QuickSight という流れで AWS のいろいろなサービスを使ってデータ分析をすることを想定していますが、今回は、S3 に格納された CSVファイルを Amazon Athena のテーブルに流し込むところまでを試してみます。

f:id:akiyoko:20170311224916p:plain



取り込むデータの考慮点

Amazon Athena は CSV、JSON、ORC、Avro、Parquet などの形式のデータを取り込むことができます。

(参考)よくある質問 - Amazon Athena | AWS


今回は、S3上の CSVデータを取り込むことを想定していますが、その際に考慮しなければいけない点がいくつかありました。

CSVデータの内容

1)カンマを取り除く

CSVデータの値として、例えば「18,900」のようにデータの途中に半角カンマ(,)が存在する場合は、最初に出現したカンマの位置までで切られてしまい、値が「18」と認識されてしまうので、データの値から半角カンマ(,)を取り除いておく必要があります。

(参考)Remove comma from value · akiyoko/marketstat@82abc4b · GitHub


2)ヘッダ行

回避策があるかもしれませんが、Athena で を取り込んだ際に、CSV のヘッダ行も1データとして取り込まれてしまうという問題があります。
今のところ解決策が無いので、次の(QuickSight での)分析フェーズでデータをフィルタリングして除去するようにしています。

パーティション

Athena を利用する際は、「パーティション」という概念が非常に重要になります。

(参考)Amazon Athenaのパーティションを理解する #reinvent | Developers.IO


例えば、S3 に保存する際のディレクトリを以下のような形式にすることで、Athena でデータを取り込む際に、「year」「month」というパーティションが使えるようになります。

marketstat(バケット)
└── boj
    ├── year=2014
    ├── year=2015
    ├── year=2016
    └── year=2017
        ├── month=01
        ├── month=02
        └── month=03
            ├── 170301.csv
            ├── 170302.csv
            ├── 170303.csv
            ・
            ・


Amazon Athena はスキャンしたデータ量に対して課金されるため、頻繁に絞り込むカラムをパーティションのキーに指定することで、読み込むデータ量を効果的に減らすことができます。パーティションに設定した項目は、SQL の WHERE句の条件に指定して絞り込むことができます。





 

テーブルを作成

いよいよ Amazon Athena の実践です。

AWS Management Console のサービス一覧から「Amazon Athena」を選択します。
f:id:akiyoko:20170311165842p:plain

現在のところ、Amaozn Athena は「N. Virginia」「Ohio」「Oregon」の3つのリージョンしか対応していません。 *1

今回は「N. Virginia」を選択します。
f:id:akiyoko:20170311165926p:plain


f:id:akiyoko:20170311170013p:plain


「Query Editor」で「Add table」をクリックして、ウィザードを使ってテーブルを作成していきます。

f:id:akiyoko:20170311170030p:plain

項目 設定値
Database: marketstat(任意)
Table Name: boj(任意)
Location of Input Data Set: s3://marketstat/boj/

なお、S3 のロケーションは、スラッシュ(/)で終了する文字列でなければいけません。

f:id:akiyoko:20170311170127p:plain


データフォーマットに「CSV」を選択します。
f:id:akiyoko:20170311170203p:plain


CSV のカラム名とタイプを一つ一つ入力していくのは面倒なので、「Bulk add columns」で一括設定します。
f:id:akiyoko:20170311170321p:plain

今回は、

date int, expected_value int, preliminary_value int, name string, confirmed_value int

と設定します。

注意点としては、CSVファイルに出力された順番にカラム名を書かないといけません。なおパーティションに含めるフィールドは、ここには含めなくてよいです。
f:id:akiyoko:20170311195617p:plain

f:id:akiyoko:20170311170441p:plain


最後にパーティションを設定します。
f:id:akiyoko:20170311170617p:plain

パーティションは、

カラム名 タイプ
year int
month int

としました。タイプを「int」形式にしないと WHERE 句で「 > 」などの演算子が使えないため、上記のように設定しました。
f:id:akiyoko:20170311170657p:plain


エラーが出なければ、テーブル作成は完了です。

ちなみに、今回作成したテーブルの DDL は以下のようになっています。

CREATE EXTERNAL TABLE IF NOT EXISTS marketstat.boj (
  `date` int,
  `expected_value` int,
  `preliminary_value` int,
  `name` string,
  `confirmed_value` int 
) PARTITIONED BY (
  year int,
  month int 
)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES (
  'serialization.format' = ',',
  'field.delim' = ','
) LOCATION 's3://marketstat/boj/';



パーティションを使用した場合は、最後に

MSCK REPAIR TABLE marketstat.boj;

を忘れずに実行しなければいけません。 非常に重要です!!!

f:id:akiyoko:20170311170758p:plain



 

テスト

Amazon Athena は内部で分散処理基盤「Presto」を使用していて、標準の ANSI SQL が利用できます。

以下のSQLを実行してみます。

SELECT * FROM boj WHERE year = 2016 AND month BETWEEN 1 AND 3 limit 100;

f:id:akiyoko:20170311200800p:plain

WHERE句でパーティションを指定しているのがポイントです。これにより、スキャンする容量を節約することができます。


例えば、全検索するとスキャン量が「1MB」だったのが・・

f:id:akiyoko:20170314204257p:plain

WHERE句の条件として「year = 2016」を指定すると、「360 KB」ほどにスキャン量が削減されているのが分かります。

f:id:akiyoko:20170314204625p:plain



結果一覧の右上のファイルアイコンをクリックすることで、結果のCSVをダウンロードすることもできます。

f:id:akiyoko:20170311200817p:plain

"date","expected_value","preliminary_value","name","confirmed_value","year","month"
,,,"name",,"2016","2"
"160210",,,"",,"2016","2"
"160210","-400","-400","銀行券要因",,"2016","2"
"160210","-28800","-29900","財政等要因",,"2016","2"
"160210","-29200","-30300","資金過不足",,"2016","2"
"160210",,," ",,"2016","2"
"160210",,," ",,"2016","2"
    ・
    ・


 

まとめ

知識ゼロから Amazon Athena を使って、S3 に格納した CSVファイルを元にデータ分析用のテーブルを作成してみました。

少し難解だったのはパーティションの概念でしたが、それが理解できればあとは直感で何とかなりそうな感じでした。

次は、QuickSight を使って実際にデータ分析していきたいと考えています。

ゼロからはじめる Scrapy(AWS でお手軽データ分析 その1/3)

Python で Web クローリング・スクレイピングするためのツールといえば、 今や Scrapy が真っ先に候補に上がりますよね。


などを見ると、Beautiful Soup4pyquery も有力候補に見えますが、Google Trends を見てみるとやはり「Scrapy」が一番人気っぽいです。







目的

最終的には下図のように、Scrapy → Amazon S3 → Amazon Athena → Amazon QuickSight という流れで、AWS のいろいろなサービスを使ってお手軽データ分析をしてみたいと考えています。

f:id:akiyoko:20170311224916p:plain

今回は以下のように、Scrapy で Web スクレイピングして作成した CSVファイルを、S3 に格納するところまでを試してみます。

f:id:akiyoko:20170311224711p:plain:w400



まずは試しに、

http://www3.boj.or.jp/market/jp/stat/jx170301.htm
f:id:akiyoko:20170304235233p:plain

をスクレイピングしてみます。


 

事前準備

Mac に Scrapy をインストールします。

使用している Python は 2系ですが、Scrapy 1.1 以降は Python 3 対応済み(ただし Windows はサポート外)なので、3系でも特に変わりはないはずです。

前提条件

ローカル環境は以下の通りです。

  • MacOS Sierra 10.12.3
  • Python 2.7.12 (Anaconda 4.2.0)

インストール

pip でインストールします。

$ pip install scrapy

Scrapy (1.3.3)


 

プロジェクト作成

ひな形を作成

scrapy startproject コマンドを実行して、プロジェクトのひな形を作成します。

$ cd ~/PycharmProjects
$ scrapy startproject marketstat
$ cd marketstat

$ tree
.
├── marketstat
│   ├── __init__.py
│   ├── items.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│       └── __init__.py
└── scrapy.cfg


 

Item を作成

まずはじめに、ひな形として作成された items.py を修正していきます。
Item クラスは、スクレイピングしたデータを入れるための箱になります。

以下のようなデータ項目を作成することにします。

項目名 変数名
日付 date
項目 name
予想値 expected_value
速報値 preliminary_value
確報値 confirmed_value


marketstat/items.py

# -*- coding: utf-8 -*-

import scrapy


class MarketstatItem(scrapy.Item):
    date = scrapy.Field()
    name = scrapy.Field()
    expected_value = scrapy.Field()
    preliminary_value = scrapy.Field()
    confirmed_value = scrapy.Field()


 

Spider を作成

次に、以下のコマンドを実行して、Spider のひな形を作成します。

$ cd marketstat
$ scrapy genspider boj www3.boj.or.jp

 
作成された Spider のひな形を以下のように修正します。

marketstat/spiders/boj.py

# -*- coding: utf-8 -*-

from datetime import datetime
import scrapy
from pytz import timezone

from ..items import MarketstatItem


class BojSpider(scrapy.Spider):
    name = "boj"
    allowed_domains = ["www3.boj.or.jp"]
    date = datetime.now(timezone('Asia/Tokyo')).strftime('%y%m%d')
    start_urls = [
        'http://www3.boj.or.jp/market/jp/stat/jx{date}.htm'.format(date=date),
    ]

    def parse(self, response):
        for sel in response.css('table tr'):
            item = MarketstatItem()
            item['date'] = self.date
            item['name'] = sel.css('td::text').extract_first()
            item['expected_value'] = sel.css('td:nth-last-child(3)::text').extract_first()
            item['preliminary_value'] = sel.css('td:nth-last-child(2)::text').extract_first()
            item['confirmed_value'] = sel.css('td:nth-last-child(1)::text').extract_first()
            yield item

parse メソッドを修正して、スクレイピングしたデータを格納した Item オブジェクトを yeild するようにします。


ちなみに、scrapy shell コマンドを実行することで、ipython が立ち上がってスクレイピングのデバッグ実行ができるため、目的の Webページの CSS要素を特定するのに便利です。

$ scrapy shell http://www3.boj.or.jp/market/jp/stat/jx170301.htm
    ・
    ・
In [1]: print(response.css('table tr')[1].css('td::text')[0].extract())
銀行券要因

(参考)http://qiita.com/ttomoaki/items/05d3dc104a695f939d63



なお、

sel.css('td::text')[0].extract()

とせず、

sel.css('td::text').extract_first()

とすることで、指定した要素が見つからない場合に IndexError が発生せずに None が返されるので非常に使い勝手がよいです。また、「::text」を付けないと HTMLタグが含まれた状態で値が取れてしまうので、これも必須のテクでしょう。


(参考)Scrapy Tutorial — Scrapy 1.5.0 documentation



 

スクレイピング実行

crawl コマンドの引数に Spider名(今回の例では「boj」)を指定します。

なお、-o オプションで出力するファイル名を指定することができます。

$ scrapy crawl boj -o test.csv


test.csv

date,expected_value,preliminary_value,name,confirmed_value
170301,,,,
170301,-600,-600,銀行券要因,
170301,"-11,100","-13,300",財政等要因,
170301,"-11,700","-13,900",資金過不足,
170301,"9,000","9,000",国債買入,
    ・
    ・


ここで、settings.py に

###############
# OUTPUT FILE #
###############
FEED_FORMAT = 'csv'
FEED_URI = '/Users/akiyoko/tmp/test.csv'

と書いておけば、-o オプションをいちいち指定する必要はありません(どちらも指定した場合は、-o オプションの方が優先されるようです)。


またここで、作成した Spider のインスタンス変数名を「FEED_URI」のプレースホルダとして使うことも可能です。

FEED_URI = '%(date)s.csv'


 

S3 に格納する方法

S3 に格納するのも簡単です。
例えば「marketstat」というバケットに格納するなら、settings.py に

###############
# OUTPUT FILE #
###############
FEED_FORMAT = 'csv'
FEED_URI = 's3://marketstat/%(name)s/%(date)s.csv'

###########
# AWS KEY #
###########
AWS_ACCESS_KEY_ID = 'AKIXXX'
AWS_SECRET_ACCESS_KEY = 'XXXXX'

とすればよいです。簡単ですね。


Spider に任意の引数を渡す

ちょっとした小技ですが、-a オプションを指定することで、Spider に任意の引数を渡すことができます。

(参考)スパイダー — Scrapy 1.2.2 ドキュメント

$ scrapy crawl boj -a date=170303


Spider 側からは、__init__ メソッド内で引数を受け取ることができます。
また、__init__ 内でインスタンス変数を上書きすることで、parse メソッドが実行される前にインスタンス変数の値を書き換えておくことが可能です。


marketstat/spiders/boj.py

class BojSpider(scrapy.Spider):
    name = "boj"
    allowed_domains = ["www3.boj.or.jp"]
    date = datetime.now(timezone('Asia/Tokyo')).strftime('%y%m%d')
    start_urls = [
        'http://www3.boj.or.jp/market/jp/stat/jx{date}.htm'.format(date=date),
    ]

    def __init__(self, date=None, *args, **kwargs):
        super(BojSpider, self).__init__(*args, **kwargs)
        if date is not None:
            self.date = date
            self.start_urls = [
                'http://www3.boj.or.jp/market/jp/stat/jx{date}.htm'.format(date=self.date),
            ]

    def parse(self, response):
        for sel in response.css('table tr'):
            item = MarketstatItem()
            item['date'] = self.date
            item['name'] = sel.css('td::text').extract_first()
            item['expected_value'] = sel.css('td:nth-last-child(3)::text').extract_first()
            item['preliminary_value'] = sel.css('td:nth-last-child(2)::text').extract_first()
            item['confirmed_value'] = sel.css('td:nth-last-child(1)::text').extract_first()
            yield item


 

シェルを作成

日付を範囲指定してクローリングするなら、例えば以下のようなシェルを作成すればよいでしょう。

#!/bin/bash

START_DATE=170220
END_DATE=170301

for (( DATE=$START_DATE ; $DATE <= $END_DATE ; DATE=`date -j -v+1d -f %y%m%d $DATE +%y%m%d` )) ; do
   echo $DATE
   scrapy crawl boj -a date=$DATE
done


(参考)



なお、大量のリクエストが発生する場合は、settings.py に

DOWNLOAD_DELAY = 3  # or 30 ?

などと設定しておきましょう。


(参考)Settings — Scrapy 1.5.0 documentation


 

まとめ

今回、知識ゼロから Scrapy を使って Web スクレイピングを試してみました。
実際使ってみて、仕組み(どこを触ればいいか)さえ分かってしまえば、実装部分も少なくて非常に便利に使えることが実感できました。

次は、S3 に格納した CSVファイルを Amazon Athena と QuickSight を使ってデータ分析していきたいと考えています。

Vagrant と Ansible を連携させて Ubuntu 14.04 に MySQL, PostgreSQL を provision する手順

AnsibleとVagrantで開発環境を構築する | さくらのナレッジ
という記事を読んで、開発環境の Ubuntu サーバに MySQL や PostgreSQL をインストールするなら、Vagrant と Ansible を連携させた方がやりやすいな、と思ったのでメモがてらまとめておきたいと思います。


ベースとしては、以前使った Ansible Galaxy を使います。
具体的には、

インストールします。


Vagrant 以外の仮想サーバや通常のサーバに対して、MySQL や PostgreSQL を簡単にインストールする手順については、以下の過去記事を参照してください。

<過去記事>
akiyoko.hatenablog.jp

akiyoko.hatenablog.jp



ホスト

  • macOS Sierra 10.12.3
  • Vagrant 1.9.1
  • VirtualBox 5.1.14


macOS Sierra に Vagrant および VirtualBox をインストールする手順については、以下を参照してください。

<過去記事>
akiyoko.hatenablog.jp


ゲスト

  • Ubuntu 14.04

ゲストマシンのプライベート IPアドレスは「192.168.33.10」に設定しておきます。


データベース

  • MySQL 5.5.54
  • PostgreSQL 9.3.16


インストールと同時に以下の設定もおこなう想定です。

項目 設定内容
データベース名 myproject
データベースユーザ myprojectuser
データベースユーザパスワード myprojectuserpass


ちなみに、MySQL の root のパスワードは「rootpass」となります。
また、PostgreSQL の場合は「postgres」という Linuxユーザが作成されます。



 

MySQL のインストール

MySQL は ANXS.mysql でインストールします。

$ cd ~/dev/vagrant/ubuntu14
$ mkdir provisioning
$ ansible-galaxy install ANXS.mysql -p provisioning/roles


etc_mysql_my.cnf.j2 ファイル内の「character_set_server」および「collation_server」のキーワードを修正します。 *1

$ sed -i '' 's/^character_set_server/character-set-server/g' provisioning/roles/ANXS.mysql/templates/etc_mysql_my.cnf.j2
$ sed -i '' 's/^collation_server/collation-server/g' provisioning/roles/ANXS.mysql/templates/etc_mysql_my.cnf.j2


provisioning/hosts

[db-servers]
192.168.33.10


provisioning/site.yml

- hosts: db-servers
  become: true
  user: vagrant
  vars_files:
    - vars/mysql.yml
  roles:
    - { role: ANXS.mysql }

 

$ mkdir provisioning/vars

provisioning/vars/mysql.yml

mysql_current_root_password: ''
mysql_root_password: rootpass
mysql_databases:
  - name: myproject
mysql_users:
  - name: myprojectuser
    pass: myprojectuserpass
    priv: "myproject.*:ALL"


最終的なディレクトリ構成はこのような感じになります。

~/dev/vagrant/ubuntu14/
├── Vagrantfile
└── provisioning
    ├── hosts
    ├── roles
    │   └── ANXS.mysql
    │       ・
    │       ・
    ├── site.retry
    ├── site.yml
    └── vars
        └── mysql.yml


Vagrantfile

    ・
    ・
  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  config.vm.network "private_network", ip: "192.168.33.10"
    ・
    ・
  # Enable provisioning with a shell script. Additional provisioners such as
  # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
  # documentation for more information about their specific syntax and use.
  # config.vm.provision "shell", inline: <<-SHELL
  #   apt-get update
  #   apt-get install -y apache2
  # SHELL
  config.vm.provision "ansible" do |ansible|
    ansible.playbook = "provisioning/site.yml"
    ansible.inventory_path = "provisioning/hosts"
    ansible.limit = "all"
  end


 

provision

サーバを初回起動する際に provision が実行されます。

$ vagrant up


起動済みのサーバには、以下のコマンドを実行することで provision が実行されます。

$ vagrant provision

  or

$ ansible-playbook -i provisioning/hosts provisioning/site.yml -k -vv

後者のコマンドの場合には、vagrant ユーザのパスワードが要求されます。

パスワードを入力したくない場合は、

$ vagrant ssh-config --host 192.168.33.10 >> ~/.ssh/config

### 念のためパーミッションを変更
$ sudo chmod 600 ~/.ssh/config

を設定しておけば、

$ ansible-playbook -i provisioning/hosts provisioning/site.yml -vv

で、パスワード無しで実行可能です。

 

動作確認

$ vagrant ssh

  or

$ ssh vagrant@192.168.33.10

でサーバにログインして、

$ mysql -u root -p

で動作確認します。




 

PostgreSQL のインストール

PostgreSQL は ANXS.postgresql でインストールします。

$ mkdir provisioning
$ ansible-galaxy install ANXS.postgresql -p provisioning/roles

provisioning/hosts

[db-servers]
192.168.33.110

provisioning/site.yml

- hosts: db-servers
  become: true
  user: vagrant
  vars_files:
    - vars/postgresql.yml
  roles:
    - { role: ANXS.postgresql }

 

$ mkdir provisioning/vars

provisioning/vars/postgresql.yml

postgresql_databases:
  - name: myproject
postgresql_users:
  - name: myprojectuser
    pass: myprojectuserpass
postgresql_user_privileges:
  - name: myprojectuser
    db: myproject
    priv: "ALL"


最終的なディレクトリ構成はこのような感じになります。

~/dev/vagrant/ubuntu14/
├── Vagrantfile
└── provisioning
    ├── hosts
    ├── roles
    │   └── ANXS.postgresql
    │       ・
    │       ・
    ├── site.retry
    ├── site.yml
    └── vars
        └── postgresql.yml


Vagrantfile

    ・
    ・
  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  config.vm.network "private_network", ip: "192.168.33.10"
    ・
    ・
  # Enable provisioning with a shell script. Additional provisioners such as
  # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
  # documentation for more information about their specific syntax and use.
  # config.vm.provision "shell", inline: <<-SHELL
  #   apt-get update
  #   apt-get install -y apache2
  # SHELL
  config.vm.provision "ansible" do |ansible|
    ansible.playbook = "provisioning/site.yml"
    ansible.inventory_path = "provisioning/hosts"
    ansible.limit = "all"
  end


 

provision

サーバを初回起動する際に provision が実行されます。

$ vagrant up


起動済みのサーバには、以下のコマンドを実行することで provision が実行されます。

$ vagrant provision

  or

$ ansible-playbook -i provisioning/hosts provisioning/site.yml -k -vv

後者のコマンドの場合には、vagrant ユーザのパスワードが要求されます。

パスワードを入力したくない場合は、

$ vagrant ssh-config --host 192.168.33.10 >> ~/.ssh/config

### 念のためパーミッションを変更
$ sudo chmod 600 ~/.ssh/config

を設定しておけば、

$ ansible-playbook -i provisioning/hosts provisioning/site.yml -vv

で、パスワード無しで実行可能です。

 

動作確認

$ vagrant ssh

  or

$ ssh vagrant@192.168.33.10

でサーバにログインして、

$ psql -U postgres
  or
$ sudo su postgres
$ psql

で動作確認します。

おまけ

### ユーザ一覧
postgres=# \du

### データベース一覧
postgres=# \l

### データベース接続
postgres=# \c myproject

### テーブル一覧
myproject=# \d
myproject=# \dt

### 終了
myproject=# \q


 

書籍

PyCharm のオレオレ最強設定


【記事更新のお知らせ】
PyCharm のバージョンアップに合わせて、本記事を全面改訂しました。🙇‍♂️
(2020.5.8)


akiyoko.hatenablog.jp


先日、「最強のPython開発環境 PyCharmのすゝめ - Qiita」という記事がホットエントリーに上がっていましたが、かくいう私も、PyCharm は Python の統合開発環境(IDE)としてまさに最強だと考えています。


www.jetbrains.com



PyCharm(を含めた JetBrains 製品)は「out of the box(アウト・オブ・ボックス:箱から取り出してすぐに使える、難しい設定などは一切なしで使える)」というのが大きな魅力のひとつですが、今回紹介する環境設定をすることで更なる実力を発揮させることができます。


今回紹介するのはあくまで私の(オレオレ)最強設定ですので、異論・反論は緩やかな範囲でお願いします。。


 

事前準備(インストール)

まずはインストールについて。

環境

  • Mac OS:10.12.3(Sierra)
  • PyCharm:2016.3.2

インストール方法

少し前までは Homebrew Cask でインストールしていたのですが、Homebrew Cask が管理している PyCharm のバージョン情報がなかなか最新化されなかったりするので、公式サイトからアプリをダウンロードしてインストールするスタイルに変えました。


ということで、https://www.jetbrains.com/pycharm/download/ から、それぞれの環境に合わせたものをダウンロードします。

f:id:akiyoko:20170309233705p:plain



私は有償の「Professional Edition」を使っていますが、無償の「Community Edition」でも大抵の機能は使えますのでご安心を。

(参考)Professional vs. Community - Compare Editions | PyCharm


なお、Professional Edition のライセンス料金はサブスクリプション方式になっていて、例えばパーソナルライセンスだと、

1年目 US $ 89.00 /年
2年目 US $ 71.00 /年
3年目以降 US $ 53.00 /年

となっています。

(参考)Buy PyCharm Professional: Pricing and Licensing, Discounts - JetBrains Toolbox Subscription





 

環境設定

上部メニューの[PyCharm] > [Preferences](ショートカットは「⌘ + ,」)から、PyCharm の環境設定が変更できます。

1.テーマ設定

[Appearance & Behavior] > [Appearance]

[UI Options]のTheme を「Darcura」(少し暗めのテーマ)に変更します。

f:id:akiyoko:20170310003027p:plain

理由:かっこいいから。

2.メモリ設定

[Appearance & Behavior] > [Appearance]

[Window Options]の「Show memory indicator」にチェックを入れます。

f:id:akiyoko:20170310003158p:plain

理由:メモリをクリアできるように。右下に使用メモリ量のインジケーターが表示されますが、これをクリックすることでメモリをクリアすることができるようになります。


f:id:akiyoko:20170310003752p:plain:w300


またここで、デフォルトのヒープメモリサイズを 2GB くらいに増やしておきます。

[Help] > [Edit Custom VM Options]

を選択すると、ファイルがまだ存在していなければ、
~/Library/Preferences/PyCharm2016.3/pycharm.vmoptions
が作成されます。

# custom PyCharm VM options

-Xms128m
-Xmx750m
-XX:ReservedCodeCacheSize=240m
-XX:+UseCompressedOops

これを、

# custom PyCharm VM options

-Xms128m
-Xmx2000m
-XX:ReservedCodeCacheSize=240m
-XX:+UseCompressedOops

などと書き換えて、PyCharm を再起動します。

(参考)IntelliJ IDEAのヒープサイズを増やす - Qiita



 

3.エディタ表示

[Editor] > [General] > [Appearance]

[Show line numbers]および[Show whitespaces]にチェックを入れます。

f:id:akiyoko:20170310003817p:plain

理由:
 ・行番号を表示したいから。
 ・空白スペースを可視化したいから。


 

4.コード補完

[Editor] > [General] > [Code Completion]

[Case sensitive completion]に「None」を選択。

f:id:akiyoko:20170310003834p:plain

理由:コード補完(control + space)の際、大文字小文字を区別しないで補完してほしいから。


 

5.タブの表示数

[Editor] > [General] > [Editor Tabs]

[Tab Closing Policy]の Tab limit を「10」から「50」に変更。

f:id:akiyoko:20170310003948p:plain

理由:タブを 10 個以上開くと勝手に閉じちゃう設定だと使いにくいから。


 

6.フォントサイズ設定

6.1. エディタのフォントサイズ

[Preferences] > [Editor] > [Color & Fonts] > [Font]

Scheme から[Save As]で、「Darcula copy」などという名前でコピーすると、フォントサイズが変更できるようになります。

「Size:10」くらいにしておきます。

f:id:akiyoko:20170310004021p:plain
f:id:akiyoko:20170310004036p:plain

理由:MacBook のディスプレイが 13インチと小さいので、できるだけ広い範囲でコードを見たいから。

6.2. コンソールのフォントサイズ

[Preferences] > [Editor] > [Color & Fonts] > [Console Font]

エディタとコンソールのフォントサイズは別々に設定が必要です。

f:id:akiyoko:20170310005558p:plain

こちらも「Size:10」くらいにしておきます。



 

7.PEP8 違反の警告

[Editor] > [Inspections]

  • [PEP8 coding style violation]
  • [PEP8 naming conversion violation]

を「week warning」から「warning」に変更。

f:id:akiyoko:20170310004146p:plain

理由:PEP8 違反の警告をワーニングに上げたいから。


(参考)最強のPython統合開発環境PyCharm - Qiita


 

8.プラグイン

[Plugins] で、以下のプラグインをインストールします。

  • CodeGlance(Sublime Text のミニマップ風)
  • gfm(GitHub Markdown プラグイン)
  • .ignore

インストールしていないプラグインは、検索して「Install」ボタンをクリックするとオンラインのリポジトリからダウンロードしてインストールすることができます。超便利。

f:id:akiyoko:20170310004817p:plain
f:id:akiyoko:20170310004832p:plain


 

9.Project ビューの表示設定

Project ビューの歯車アイコンをクリックして、

  • [Autoscroll to Source](Projectビューからシングルクリックでソースビューを開く)
  • [Autoscroll from Source](ソースビューを開くとProjectビューに該当モジュールがフォーカスする)

にチェックを入れておきます。

f:id:akiyoko:20170310010217p:plain



 

便利なショートカットなど

最後に非常に便利なショートカットをいくつか紹介します。

JetBrains 公式のチートシート もありますが、さすがに全部覚えきれませんよね。。

 

検索
ショートカット 説明
shift + ⌘ + f grep検索
opt + ⌘ + ↓ (↑) grep検索結果の前後を表示
⌘ + f ファイル内検索
(shift +) ⌘ + g ファイル内検索結果の前後を表示
⌘ + b 関数・メソッドの宣言にジャンプ
opt + ⌘ + F7 関数・メソッドを使用している箇所を検索
opt + ⌘ + ← (→) 履歴の前後を表示

 

タブ移動
ショートカット 説明
control + ← (→) タブ移動

 

ファイルを開く
ショートカット 説明
shift x 2 (素早く2回) クイック検索
⌘ + e 最近開いたファイルを開く
shift + ⌘ + o ファイル名でファイルを開く

 

差分表示
ショートカット 説明
(Project ビューで) ⌘ + d 別ファイルとの Diff

 

その他
ショートカット 説明
⌘ + ↑ ナビゲーションバーを操作
opt + F12 Terminal を開閉
shift + ⌘ + a 利用できるアクションを検索


 

まとめ

デフォルトの環境設定を何も弄らなくても、PyCharm は快適に動いてくれます。
しかしながら、今回のような環境設定をすると、PyCharm はその秘めたる力を開放してくれます。まだ見つけられていない素敵な設定を見つけたら、この記事をどんどん更新していきます。

それでは、素敵な PyCharm ライフを!