Jupyter NotebookからPythonでPostgreSQL 13を操作する

まずは堅実に定番RDBから

 NoSQLブームで勉強会界隈では影が薄れた感があるRDBMSですが、当たり前の存在になっただけで健在です。当サイトが使っているCMS『WordPress』にはMySQLが組み込まれていますし、当サイトを置いているAWSでもPostgreSQLがRedShiftやAuroraなど基幹サービスで使われています

 統計モデリングを知らないのにディープラーニングでイキる人が信用出来ないのと同様、RDBMSを知らないのにNoSQLを語るのもイタい人です。私も昨年はElasticsearchなどのNoSQLやApache Igniteなどの分散データベースにハマっていましたが、自分のイタさに気づいたためPostgreSQLを学び直しています。

 NoSQLや分散データベースを業務で使いこなしている方々の講演は勉強会で聞くことが出来ますが、彼らは「RDBMSを経験した上でNoSQLに進んでいる」のです。未経験者が講演を聞いていきなりNoSQLに行くのは『悪手』です。

初心者がNoSQLや分散RDBでやりたいことはPostgreSQLでも出来ている

 NoSQLが主に扱うJSONはPosgreSQLでも『JSON型』として扱えます。IMDBで話題となったインメモリ処理はPostgreSQLでも駆使されています。分散データベースの特徴であるクエリの並列処理はPostgreSQLでもパラレルクエリとして実装されている上、特別の設定無しで自動的に使ってくれます。

 昨年、実際にApage IgniteでPCサーバ5台のクラスタを構築しましたが、数10GB程度のデータではクエリに要する時間がシングルノードのPostgreSQLの方が速かったです。2.5GbEでもLANの遅延で並列処理のメリットが相殺されてしまいます。この規模のデータでクエリを高速化するなら、32コア64スレッドのパソコンでも買えば良いと思います。

 こういう事実は、基本的すぎて勉強会ではなかなか教えてもらえないですね。エンジニアの方にとっては常識なのでしょうが……💦

PythonでPostgreSQLを扱うパッケージ『psycopg2』

 PythonでPostgreSQLを扱う際は『psycopg2』というパッケージを使うのが一般的なようです。視覚化のためのパッケージ『Plotly』と併せてインストールします。

(base) masaru@ASUS-TUF-Gaming:~$ conda install psycopg2
(base) masaru@ASUS-TUF-Gaming:~$ conda install plotly

Jupyter Notebookでの実装

 必要なパッケージの読み込みなどの初期設定を行います。

# -*- coding: utf-8 -*-
import psycopg2
import pandas as pd
import plotly as py
import plotly.graph_objs as go
from plotly.offline import iplot, init_notebook_mode
init_notebook_mode()

 psychopg2でデータベース接続を定義します。

def connect():
    con = psycopg2.connect("host=" + "localhost" +
                           " port=" + "5432" +
                           " dbname=" + "google_mobility" +
                           " user=" + "masaru" +
                           " password=" + "xxxxxxxxxxxx")
    return con

 続いて、クエリを定義します。

def select_execute(con, sql):
    with con.cursor() as cur:
        cur.execute(sql)
        rows = cur.fetchall()

    return rows

 定義したクエリを実行します。

con = connect()
sql =  "select * from google_mobility where SUB_REGION_1 = 'Tokyo'"
result = select_execute(con, sql)

 クエリの結果をPandasデータフレームに代入します。

df = pd.DataFrame(result)
df.head()

 とりあえず全部カラム名をつけてあげます。

columns = ["id","country_region_code","country_region","sub_region_1","sub_region_2","metro_area","iso_3166_2_code","census_fips_code","date","retail","grocery","parks","transit","workplaces","residental","place_id"]
df.columns = columns
df.head()

 Plotlyで表示するデータを設定します。

trace1 = go.Scatter(
        x = list(df.date),
        y = list(df.retail),
        mode = 'lines+markers',
        name = 'retail',
        marker = dict(
                color = 'blue'
                )
        )

trace2 = go.Scatter(
        x = list(df.date),
        y = list(df.grocery),
        mode = 'lines+markers',
        name = 'grocery',
        marker = dict(
                color = 'orange'
                )
        )

data = [trace1, trace2]

 続いて、Plotlyの特長であるスライダーを設定します。

layout = dict(
    title='Time series with range slider and selectors',
    xaxis=dict(
        rangeselector=dict(
            buttons=list([
                dict(count=1,
                     label='1m',
                     step='month',
                     stepmode='backward'),
                dict(count=6,
                     label='6m',
                     step='month',
                     stepmode='backward'),
                dict(count=1,
                    label='YTD',
                    step='year',
                    stepmode='todate'),
                dict(count=1,
                    label='1y',
                    step='year',
                    stepmode='backward'),
                dict(step='all')
            ])
        ),
        rangeslider=dict(
            visible = True
        ),
        type='date'
    )
)

 グラフを描画します。

fig = dict(data=data, layout=layout)
iplot(fig)

 スライダーを動かせるグラフの出来上がりです。

Ubuntu 21.04にPostgreSQL 13をインストールする

準備

sudo apt install curl ca-certificates gnupg
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
sudo apt update

 PosgreSQLをaptのレポジトリに登録します。

インストール

(base) masaru@ASUS-TUF-Gaming:~$ sudo apt install postgresql
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています... 完了        
状態情報を読み取っています... 完了        
以下の追加パッケージがインストールされます:
  postgresql-13 postgresql-client-13 postgresql-client-common
  postgresql-common sysstat
提案パッケージ:
  postgresql-doc postgresql-doc-13 libjson-perl isag
以下のパッケージが新たにインストールされます:
  postgresql postgresql-13 postgresql-client-13 postgresql-client-common
  postgresql-common sysstat
アップグレード: 0 個、新規インストール: 6 個、削除: 0 個、保留: 6 個。
15.6 MB のアーカイブを取得する必要があります。
この操作後に追加で 48.1 MB のディスク容量が消費されます。
続行しますか? [Y/n] Y

 デフォルトではバージョン13が入るようです。

(base) masaru@ASUS-TUF-Gaming:~$ sudo systemctl status postgresql
● postgresql.service - PostgreSQL RDBMS
     Loaded: loaded (/lib/systemd/system/postgresql.service; enabled; vendor pr>
     Active: active (exited) since Mon 2021-04-05 17:36:05 JST; 6min ago
   Main PID: 116889 (code=exited, status=0/SUCCESS)
      Tasks: 0 (limit: 37748)
     Memory: 0B
     CGroup: /system.slice/postgresql.service

 4月 05 17:36:05 ASUS-TUF-Gaming systemd[1]: Starting PostgreSQL RDBMS...
 4月 05 17:36:05 ASUS-TUF-Gaming systemd[1]: Finished PostgreSQL RDBMS.

無事動いているようです。

PgAdmin 4は未対応の模様

PgAdminはまだUbuntu 21.04に未対応のようです。

(base) masaru@ASUS-TUF-Gaming:~$ sudo apt install pgadmin4
[sudo] masaru のパスワード: 
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています... 完了        
状態情報を読み取っています... 完了        
パッケージ pgadmin4 は使用できませんが、別のパッケージから参照されます。
これは、パッケージが欠落しているか、廃止されたか、または別のソース
からのみ利用可能であることを意味します。

E: パッケージ 'pgadmin4' にはインストール候補がありません

LANからPostgreSQLに接続可能にする設定

(base) masaru@ASUS-TUF-Gaming:~$ sudo gedit /etc/postgresql/13/main/pg_hba.conf 
(base) masaru@ASUS-TUF-Gaming:~$ sudo systemctl restart postgresql
# IPv4 local connections:
host    all             all             127.0.0.1/32            md5
host    all             all         192.168.100.1/24            md5

ユーザーの作成

postgres=# create role masaru with superuser login;
select * from pg_shadow;

 usename  | usesysid | usecreatedb | usesuper | userepl | usebypassrls | passwd | valuntil | useconfig 
----------+----------+-------------+----------+---------+--------------+--------+----------+-----------
 postgres |       10 | t           | t        | t       | t            |        |          | 
 masaru   |    16384 | f           | t        | f       | f            |        |          | 
(2 rows)

postgres=# alter role masaru with password 'xxxxxxxxxxx';
ALTER ROLE

データベースの作成

 『Google コミュニティ モビリティ レポート』のデータベースを作ってみます。

(base) masaru@ASUS-TUF-Gaming:~$ createdb google_mobility

 DBeaverで接続します。

 DBeaverでSQL文を実行してテーブルを作成します。

DBeaverでテーブルを作成する
create table google_mobility( 
	id SERIAL primary key, 
	country_region_code varchar(2) , 
	country_region varchar(255) , 
	sub_region_1 varchar(255) , 
	sub_region_2 varchar(255) , 
	metro_area varchar(255) , 
	iso_3166_2_code varchar(32) , 
	census_fips_code varchar(32) , 
	date date , 
	retail_and_recreation_percent_change_from_baseline integer , 
	grocery_and_pharmacy_percent_change_from_baseline integer , 
	parks_percent_change_from_baseline integer , 
	transit_stations_percent_change_from_baseline integer , 
	workplaces_percent_change_from_baseline integer , 
	residential_percent_change_from_baseline integer);

CSVデータのインポート

 いよいよCSVのインポートです。DBeaverはCSVも格納先フォルダをデータベースとして登録してからPostgreSQLにインポートするのがお作法のようです。

 データベース上のテーブルとCSVでカラム名が一致していれば、マッピングで困ることはないようです。

 とは言え、Google Mobilityデータも短期間のうちにplace_idというカラムが増えており、オープンデータの扱いのやっかいさを実感します。さらにCSVの中身が壊れているとインポートの段階で苦しみます……💦

 増えていたカラムはvarchar(32767)で格納されてしまっているので、varchar(32)に縮小します。

select place_id from google_mobility limit 10;
alter table google_mobility alter column place_id type varchar(32);

SQL select文でデータ抽出

select count(*) from google_mobility;
select * from google_mobility
	where sub_region_1 = 'Tokyo'
	order by date desc;
Google Community Mobility ReportからSQL select文でデータを抽出する

 4838804レコードから400ミリ秒ほどでselect文を実行できました。緊急事態宣言が解除された東京ですが、まだ交通、職場、小売店などでは自粛の影響が残っているようです。