データサイエンス100本ノックで勉強(3)

前回の記事までSQLでデータサイエンス100本ノックの問題を解いていこうと考えていたのですが、やっぱり方針変えてPythonも同時進行で進めていきたいと思います。
細かいPandasの文法は現時点では一つもわかりませんが、解答を写経しながら調べていきます。

まずJupyter Notebookからpsycopg2を使って以下のようにDBに接続します。

import os
import pandas as pd
import numpy as np
from datetime import datetime, date
from dateutil.relativedelta import relativedelta
import math
import psycopg2
from sqlalchemy import create_engine
from sklearn import preprocessing
from sklearn.model_selection import train_test_split
from imblearn.under_sampling import RandomUnderSampler

pgconfig = {
    'host': 'db',
    'port': os.environ['PG_PORT'],
    'database': os.environ['PG_DATABASE'],
    'user': os.environ['PG_USER'],
    'password': os.environ['PG_PASSWORD'],
}

# pd.read_sql用のコネクタ
conn = psycopg2.connect(**pgconfig)

df_customer = pd.read_sql(sql='select * from customer', con=conn)
df_category = pd.read_sql(sql='select * from category', con=conn)
df_product = pd.read_sql(sql='select * from product', con=conn)
df_receipt = pd.read_sql(sql='select * from receipt', con=conn)
df_store = pd.read_sql(sql='select * from store', con=conn)
df_geocode = pd.read_sql(sql='select * from geocode', con=conn)

dictionary型のpgconfigに接続情報を格納していて、その接続情報はどこで定義しているのか探したところdocker-compose.ymlで以下のように定義されていました。

  notebook:
    build:
      context: .
      dockerfile: dockerfiles/notebook/Dockerfile
    image: dss-notebook
    container_name: dss-notebook
    ports:
      - "8888:8888"
    environment:
      - PG_PORT=5432
      - PG_USER=padawan
      - PG_PASSWORD=padawan12345
      - PG_DATABASE=dsdojo_db
      - JUPYTER_ENABLE_LAB=yes

おそらくdocker-compose.ymlで定義されてJupyter Notebookコンテナの環境変数のになり、os.environ[変数名]で取得できるようになっているはず。

そしてその環境変数をJupeyter Notebookで以下のようにpsycopg2のconnect関数に渡しています。

pgconfig = {
    'host': 'db',
    'port': os.environ['PG_PORT'],
    'database': os.environ['PG_DATABASE'],
    'user': os.environ['PG_USER'],
    'password': os.environ['PG_PASSWORD'],
}

# pd.read_sql用のコネクタ
conn = psycopg2.connect(**pgconfig)

psycopg2のドキュメントは以下だと思います。

www.psycopg.org

psycopg2もまだよくわかっていませんが、どりあえずPythonからPostgresqlを操作できるライブラリと認識しています。

psycopg2に関してはこちらの記事がわかりやすかったです。

qiita.com

DBに接続した後にpandasのread_sql関数を使ってデータを取得しています。

df_customer = pd.read_sql(sql='select * from customer', con=conn)
df_category = pd.read_sql(sql='select * from category', con=conn)
df_product = pd.read_sql(sql='select * from product', con=conn)
df_receipt = pd.read_sql(sql='select * from receipt', con=conn)
df_store = pd.read_sql(sql='select * from store', con=conn)
df_geocode = pd.read_sql(sql='select * from geocode', con=conn)

pandasの公式ドキュメントに関数の説明がありました。

pandas.pydata.org

pandas.read_sql(sql, con, index_col=None, coerce_float=True, params=None, parse_dates=None, columns=None, chunksize=None)

と書かれているので第三引数以降はオプションのようです。

また、以下の記載があったので返す値はデータフレームという型になっているようです。

Returns DataFrame or Iterator[DataFrame]

上記のコードを実行しPostgresqlに接続した後、以下のコードでデータを取得しました。

df_receipt.head(10)

f:id:JunpeiNakasone:20220124055627p:plain

head関数は以下の説明がありました。

pandas.pydata.org

デフォルトは5件になっているようで
f:id:JunpeiNakasone:20220124060012p:plain

-をつけると最後から指定した行だけを除いた全ての行を取得するようです。
f:id:JunpeiNakasone:20220124060232p:plain df_reciptは104681件あり、head(-1)で104680件取得しています。

SQLで先に解いていた問題をPythonでも書いてみます。
SQLは自分でゼロから書いてみてそれが合ってるか解答と比べる感じで進めてましたが、pandasの構文は全然わからないので写経していきます。

S-002: レシート明細のテーブル(receipt)から売上日(sales_ymd)、顧客ID(customer_id)、商品コード(product_cd)、売上金額(amount)の順に列を指定し、10件表示させよ。

上記のデータは以下のコードで取得できました。

df_receipt[['sales_ymd', 'customer_id', 'product_cd', 'amount']].head(10)

f:id:JunpeiNakasone:20220124061128p:plain

'sales_ymd', 'customer_id', 'product_cd', 'amount'ってという書き方がよくわからなかったので['sales_ymd', 'customer_id', 'product_cd', 'amount']にしていみると以下のようにKeyErrorになりました。
これはこういう書き方なんだなぐらいの認識で進めます。
f:id:JunpeiNakasone:20220124061338p:plain

P-003: レシート明細のデータフレーム(df_receipt)から売上日(sales_ymd)、顧客ID(customer_id)、商品コード(product_cd)、売上金額(amount)の順に列を指定し、10件表示させよ。ただし、sales_ymdはsales_dateに項目名を変更しながら抽出すること。

上記のデータは以下のコードで取得できました。(\をつけて改行できるようです)

df_receipt[['sales_ymd', 'customer_id', 'product_cd', 'amount']]\
.rename(columns={'sales_ymd':'sales_date'}).head(10)

f:id:JunpeiNakasone:20220124061735p:plain

別名をつける構文はpandasだとrename関数を使う必要があるみたいなのでSQLの方がasをつけるだけでシンプルに書けそうだなと感じました。

次回からSQLPython両方で進めていきます。