コンピューターシステム株式会社

技術BLOG

Python使ってデータ分析(後編)

言語 2019/07/12 中部担当

こんにちは。中部営業所のpopoです。

後編となります。宜しくお願いします。

データ解析ライブラリ

NumPyとPamdas

Pythonには様々なライブラリがありますが、必ず利用するのが NumPy と Pandas らしいです。両者ともデータを扱うライブラリみたいですが、何が違うんでしょう。

色々調べてみたところ、Pandasは NumPyを利用して色々拡張されたライブラリの様です。

NumPyでは多次元配列の数値データ処理にのみ用いられますが、PandasではNumPyを拡張して Excelや CSVをデータソースとした計算および加工が出来るようになっているみたいです。

ただし、Pandasではその構造の複雑さ故に計算スピードが遅く、機械学習などでは使われないとの事。Pandasはデータソースの読み込みとデータ加工までが範疇で、実際の演算は NumPyに任せた方が良いそうです。

ではさっそく Pandasをインストールしてみましょう。NumPyも一緒にインストールされるはずです。

PS C:\Users\popo> pip install pandas
Collecting pandas
  Downloading https://files.pythonhosted.org/packages/5a/a2/aac2ebc59339b39f0f07f3f3bc8669e1205457112be7
  f7aa0875086ea2db/pandas-0.24.1-cp36-cp36m-win_amd64.whl (8.8MB)
    100% |████████████████████████████████| 8.8MB 1.8MB/s
Collecting python-dateutil>=2.5.0 (from pandas)
  :

問題なく Pandas と NumPyがインストールされたようです。pip の listコマンドでも確認してみます。

PS C:\Users\popo> pip list
Package    Version
---------- -------
numpy           1.16.2
pandas          0.24.1
pip        18.1
python-dateutil 2.8.0
pytz            2018.9
setuptools 40.6.2
six             1.12.0
You are using pip version 18.1, however version 19.0.3 is available.
You should consider upgrading via the 'python -m pip install --upgrade pip' command.

NumPy

では簡単に Pandasと NumPyを使ってみたいと思います。まずは NumPyからです。

NumPy には np.ndarrayと呼ばれる多次元配列を扱う型があり、単に配列と呼んでいます。np.ndarray と Pythonのリストを比較すると、①要素の型は同じでなければならない。②各次元ごとの要素数が等しくなければならない。といった違いがあります。

np.ndarrayは array関数にリストかタプルを指定して生成します。ny.ndarrayのコンストラクタを使う事も可能ですが、今回は利用しません。

>>> a = np.array([1, 2, 3])
>>> a
array([1, 2, 3])
>>> b = np.array((10, 20, 30))
>>> b
array([10, 20, 30])
>>> c = np.array([a, b])
>>> c
array([[ 1,  2,  3],
       [10, 20, 30]])

四則演算をやってみます。Pythonの リストでは単に連結されていただけでしたが、ny.ndarrayでは各要素に対して四則演算が行われます。

>>> a - b
array([ -9, -18, -27])
>>> a * b
array([10, 40, 90])
>>> c * a
array([[ 1,  4,  9],
       [10, 40, 90]])
>>> c - a
array([[ 0,  0,  0]
       [ 9, 18, 27]])

配列の型は dtypeで確認できます。また、型を指定する場合は np.array()関数の引数で dtypeを指定します。

>>> a.dtype
dtype('int32')
>>> a = np.array([1, 2, 3], dtype=float)
>>> a.dtype
dtype('float64')
>>> a
array([1., 2., 3.])

要素を直接指定する場合は角括弧を使います。

>>> a[0]
1
>>> a[1:3]
array([2., 3.])
>>> c[1,2]
30
>>> c[1] 
array([10, 20, 30])

Pandas

左記にも書きましたが、Pandasは Excelや CSVをデータソースとした計算および加工が出来るライブラリです。Pandasでは読込んだデータを DataFrameという型のオブジェクトとして保持します。今回は、CSV、Excelからの読み込みを試してみた後、RDB(Oracle)からデータを取得して、Pandasで加工してみる事にします。

CSV読込

CSVファイルの読み込みには Pandasの read_csv()関数を使用します。読込用サンプルデータ:[sales.csv]

>>> import pandas as pd
>>> df = pd.read_csv('sales.csv', encoding='CP932', parse_dates=['営業日'])
>>> df
    商品コード   商品名  売価        営業日 曜日  販売数   販売店
0    T001   ちくわ  80 2019-03-01  金   16  一番町店
1    T001   ちくわ  80 2019-03-02  土   42  一番町店
2    T001   ちくわ  80 2019-03-03  日   45  一番町店
  :

今回はCSVファイルをWindowsで作成している為、文字コードに 'CP932'を指定しています。また、「営業日」をTimeStamp型として読み込みたいため、parse_datesで「営業日」を指定しています。

read_csv()関数の引数には、文字コードの指定やカラムに対する日付型での読込み指定以外にも1行目の扱いを決める headerやコメントなど、読込対象から外したい行を読み飛ばすための skiprowsなどがあります。

以下の例では、1行目をデータレコードとして扱い、タブ文字をセパレータとしてUTF-8のファイルを読み込みます。

>>> df = pd.read_csv('sample.csv', sep='\t')

index_col という引数があり、データにインデックスを付与することが可能です。index_colを指定しない場合は左記のように自動で0からの連番が付与されてインデックスとして利用できます。

下記は「商品コード」、「営業日」、「販売店」をインデックスとして指定してデータを読み込んでいます。

>>> df = pd.read_csv('sales.csv', encoding='CP932', parse_dates=['営業日'], index_col=['商品コード', 
    '営業日', '販売店'])
>>> df
                        商品名  売価 曜日  販売数
商品コード 営業日        販売店
T001  2019-03-01 一番町店   ちくわ  80  金   16
      2019-03-02 一番町店   ちくわ  80  土   42
      2019-03-03 一番町店   ちくわ  80  日   45
      2019-03-04 一番町店   ちくわ  80  月   32
        :
Excel読込

Excelファイルの読み込みには Pandasの read_excel()関数を使用します。

また、Excelファイルの読み込み用のライブラリである xlrd が必要となります。(書込み用のライブラリは、xlwt)

まずは、pipで xlrdをインストールします。ついでにExcelファイルへの書き込み用ライブラリである xlwtもインストールしておきます。

PS C:\Users\popo> pip install xlrd
PS C:\Users\popo> pip install xlwt

CSVファイルの読み込みで使ったCSVファイルからExcelブックを作って読み込んでみます。

>>> import pandas as pd
>>> df = pd.read_excel('sales.xlsx', sheet_name='売上', index_col=[0, 3, 6])
>>> df
                        商品名  売価 曜日  販売数
商品コード 営業日        販売店
T001  2019-03-01 一番町店   ちくわ  80  金   16
      2019-03-02 一番町店   ちくわ  80  土   42
      2019-03-03 一番町店   ちくわ  80  日   45
      2019-03-04 一番町店   ちくわ  80  月   32
        :

CSVファイルの読込み時に指定した引数との違いとしては、文字コードの指定がなくなった事と、読込対象のシート名を明示している事ぐらいです(引数でシート名を指定しなかった場合、1番目のシートが読み込まれます)。ただ、インデックスを文字列で指定することが出来ないらしく、カラム番号で指定しています。

Oracleのテーブル読込

Oracleと接続するためには PythonのOracle接続用ライブラリである cx_Oraclel および、Oracle Instant Clientが必要になります。

まずは、cx_Oraclel を pipでインストールしましょう。

PS C:\Users\popo> pip install cx_Oracle

Instant Clientをインストールしていない場合は、OTL[https://www.oracle.com/downloads/]から 接続するOracleのバージョンに合わせた instantclient-basicをダウンロードしてきてインストールしてください。(ダウンロードには無料のユーザー登録が必要です)

ダウンロードしてきたzipファイルを解凍して出来る instantclient_11_2フォルダを任意の場所に置き、instantclient_11_2フォルダを環境変数のPATHに追加してください。

popoはCドライブの直下に Oracleというフォルダを作り、更にOracleフォルダの下に instantclientフォルダを作って、instantclientフォルダの下にinstantclient_11_2フォルダを配置しています。

これで、cx_Oracle を使う準備が整いました。早速Oracleへ繋いでいきたいと思います。

今回は以下の様なテーブルが存在しているものとして話を進めます。

■Oracle接続情報
OracleサーバIPアドレス 192.168.80.10
ポート番号 1521
SID cscdb
ユーザ popodb
パスワード popodb
カラム名 説明
■SALES(販売実績テーブル)
PRODUCT_CODE char(4) 商品コード
PRODUCT_NAME varchar2(50) 商品名
SALES_PRICE number(4, 0) 売価
TRANSACTION_DATE date 営業日
DAY_OF_WEEK varchar2(10) 曜日
SALES_VOLUME number(5, 0) 販売数
STORE_NAME varchar2(90) 販売店

まずは cx_Oracleと必要なライブラリをインポートします。

>>> import cx_Oracle
>>> import pandas as pd
>>> import os

次に環境変数 NLS_LANGに JAPANESE_JAPAN.JA16SJISTILDEを設定します。

>>> os.environ['NLS_LANG'] = 'JAPANESE_JAPAN.JA16SJISTILDE'

DB情報を定数に設定していきます。

>>> HOST = '192.168.80.10'
>>> PORT = 1521
>>> SID = 'cscdb'
>>> USER = 'popodb'
>>> PASS = 'popodb'

Pythonの open()関数を使って SQLの書かれたファイルを読み込んでおきます。ここでは withブロックを使用して 自動的にcloseするようにしています。

>>> with open('SALES_SELECT.sql', 'r', encoding='utf-8') as f:
...      sql = f.read()

>>>

Oracleに接続し、SQLを発行して結果を DataFrameに取り込みます。

>>> tns = cx_Oracle.makedsn(HOST, PORT, SID)
>>> conn = cx_Oracle.connect(USER, PASS, tns)
>>>
>>>
>>> base = pd.read_sql_query(sql, conn)
>>>
>>> conn.close
<built-in method close of cx_Oracle.Connection object at 0x000001F49F5351F0>

テーブルからのデータ読込に成功したようです。

>>> print(type(base))
<class 'pandas.core.frame.DataFrame'>
>>> print(base)
    PRODUCT_CODE PRODUCT_NAME  SALES_PRICE TRANSACTION_DATE DAY_OF_WEEK  SALES_VOLUME STORE_NAME
0           T005          たまご          110       2019-03-11           月            33       一番町店
1           T005          たまご          110       2019-03-12           火            18       一番町店
2           T005          たまご          110       2019-03-13           水            28       一番町店
3           T005          たまご          110       2019-03-14           木            25       一番町店
  :
990         T008         はんぺん           90       2019-03-30           土            57       大街道店
991         T008         はんぺん           90       2019-03-31           日             6       大街道店

[992 rows x 7 columns]

売価と販売数をかけて、1日当たりの店舗ごとの商品の売上を計算し、元のDataFrameに SALES列として追加してみます。

>>> base['SALES'] =  base.SALES_PRICE * base.SALES_VOLUME
>>> print(base)
    PRODUCT_CODE PRODUCT_NAME  SALES_PRICE TRANSACTION_DATE DAY_OF_WEEK  SALES_VOLUME STORE_NAME  SALES
0           T005          たまご          110       2019-03-11           月            33       一番町店   3630
1           T005          たまご          110       2019-03-12           火            18       一番町店   1980
2           T005          たまご          110       2019-03-13           水            28       一番町店   3080
  :

少し集計っぽい事をしてみましょう。各店舗ごとに曜日別の売上を集計して上位10番までを表示してみます。

まずは表示する列をリストで指定して、groupby()関数で合計を集計します。デフォルトでは集計グループが Indexになってしまう為、as_index=Falseを指定して 集計グループが Indexにならないようにしています。

最後に上位10番までを表示するため、角括弧で要素を指定しています。

>>> top10 = base[['STORE_NAME', 'DAY_OF_WEEK', 'SALES']].groupby(['STORE_NAME', 'DAY_OF_WEEK'], 
    as_index=False).sum().sort_values('SALES', ascending=False)[0:10]
>>> top10
   STORE_NAME DAY_OF_WEEK   SALES
24       大街道店           木  163860
0        一番町店           土  142510
26       大街道店           火  141740
23       大街道店           月  140760
1        一番町店           日  135770
25       大街道店           水  134400
27       大街道店           金   91760
6        一番町店           金   86260
21       大街道店           土   80310
2        一番町店           月   77650

大街道店が調子いい見たいですね。でも土日は一番町店の方が優勢みたいです。

まとめ

<>今回はPythonの環境を作成し、データ解析ライブラリの NumPy と Pandasをインストールし使ってみました。率直な感想としては「SQLで良いんじゃね?」となるのですが、例えばDBリンクが使えない2つのDBの情報を取り込んで解析したり、DBとExcelの情報から解析を行う。なんて時には便利だと思います。

今後は、もう少し勉強を進めて TensorFlow、使ってみようと思います。