SQLエンジニアのためのPandas入門

バッチ処理などで一時テーブルにCSVを投入してデータを処理してましたが
Pandasを使ってみたら高速で尚且つSELECT文を扱うようにCSVの操作が
出来たのでまとめて見ました。
Pandasでデータ分析と聞くと難しそうで距離をおいてましたが
大量データの処理はPandasでいこうかと思っています
Excelで大量のデータを扱ってる人にもExcelを直接開くことが出来るのでお勧めです

- [前準備](#前準備)
- [CSV読み込み](#csv読み込み)
- [SELECT](#select)
- [OREDER BY](#order-by)
- [WHERE](#where)
- [MAX, MIN, AVG, SUM, COUNT](#max-min-avg-sum-count) 
- [GROUP BY](#group-by)
- [JOIN](#join)
- [AS](#as)
- [OFFSET FETCH LIMIT](#offset-fetch-limit)
- [INSERT](#insert)
- [UPDATE](#update)
- [DELETE](#delete)

## 前準備
- pandas　
データ解析機能を提供するpythonライブラリ
pipでpandasをインストール
``` python:command line
 pip install pandas
 # または　pip3 install pandas
```
今回使用するCSVファイルがこちらになります
``` csv: hoge_a.csv
name,salary,postcode, birthday
山田, 200,   038-0056, 2004/1/3
佐藤, 300,   300-0727, 1999/6/30
鈴木, 500,   132-0013, 1980/3/3
川田, 700,   300-0727, 1975/11/23
山田, 250,   038-0056, 2004/1/25
山田, 350,   030-0932, 1987/9/12
```
``` csv: hoge_b.csv
name,address
山田,    青森
田中,    秋田
佐藤,    茨城
鈴木,    東京
```
``` csv: hoge_c.csv
name,salary,age
山田,   200, 20
山田,   350, 44
佐藤,   300, 25
鈴木,   500, 37
```
※実際に使う場合は空白の部分を詰めてください
## CSV読み込み
``` python: PandasCsv.py
import pandas as pd

df_a = pd.read_csv("./hoge_a.csv")
df_b = pd.read_csv("./hoge_b.csv")
```

read_csvでCSVをDataFrameオブジェクトとして読み込んでいます
パラメータを指定していないので、一行目が列名になります

`pd.read_csv("./hoge_a.csv",names=['a','b','c','d'])`のようにすればnamesの部分を列名にすることもできます
(その場合には、一行目もデータとして扱われます)
[read_csvのパラメータ一覧](https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html)


## SELECT
``` python: PandasCsv.py
# SELECT name, salary FROM df_a
print(df_a[["name", "salary"]])
```
実行結果
``` python:command line
    name  salary
0   山田     200
1   佐藤     300
2   鈴木     500
3   川田     700
4   山田     250
5   山田     350
```
DataFrameから指定した列名の列を選択しています
`["列名1", "列名2"]`のように複数の列名をリストで渡します


## ORDER BY
``` python: PandasCsv.py
# SELECT * FROM df_a ORDER BY name DESC, salary DESC
print(df_a.sort_values(by=["name", "salary"], ascending=False, ignore_index=True))
```
実行結果
``` python:command line
    name  salary  postcode    birthday
0   鈴木     500  132-0013    1980/3/3
1   川田     700  300-0727  1975/11/23
2   山田     350  030-0932   1987/9/12
3   山田     250  038-0056    2004/1/25
4   山田     200  038-0056    2004/1/3
5   佐藤     300  300-0727   1999/6/30
```
`sort_values`で渡した列名のリストをターゲットにして並び替えています(リストの最初に指定した列が優先されます)

#### 使用しているパラメータ
- **by**(必須)：並び替えの対象にする列名
- **ascending**：昇順かどうか(Falseで降順)
- **ignore_index**：並び順を変更する前のindexを無視するかどうか
(Trueでindexを新しく振りなおします)


[sort_valuesのパラメータ](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.sort_values.html#pandas.DataFrame.sort_values)

日付はCSVから読み込んだ段階では、`str`なので(例：3と25が正しく並び変わらない)`ORDER BY`する際には、日付型に変更する必要があります
``` python: PandasCsv.py
# SELECT * FROM df_a ORDER BY birthday DESC
df_a["birthday"] = pd.to_datetime(df_a["birthday"])
print(df_a.sort_values(by=["birthday"], ascending=False, ignore_index=True))
```
実行結果
``` python:command line
変換なし
    name  salary  postcode    birthday
0   山田     200  038-0056    2004/1/3
1   山田     250  038-0056   2004/1/25
2   佐藤     300  300-0727   1999/6/30
3   山田     350  030-0932   1987/9/12
4   鈴木     500  132-0013    1980/3/3
5   川田     700  300-0727  1975/11/23
変換あり
    name  salary  postcode   birthday
0   山田     250  038-0056 2004-01-25
1   山田     200  038-0056 2004-01-03
2   佐藤     300  300-0727 1999-06-30
3   山田     350  030-0932 1987-09-12
4   鈴木     500  132-0013 1980-03-03
5   川田     700  300-0727 1975-11-23
```

## WHERE
``` python: PandasCsv.py
# SELECT * FROM df_a WHERE salary >= 400 AND postcode == "300-0727"
print(df_a[(df_a["salary"] >= 400) & (df_a["postcode"] == "300-0727")])
# または
print(df_a.query("salary >= 400 and postcode == '300-0727'"))
```
実行結果
``` python:command line
    name  salary  postcode    birthday
3   川田     700  300-0727  1975/11/23
```
df_a[条件]で条件にあった行を抽出できます
または`query`に条件式を渡しても同じように行を抽出できます

[queryのパラメータ](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.query.html#pandas.DataFrame.query)

`df_a[(df_a["salary"] >= 400) & (df_a["postcode"] == "300-0727")]`や`df_a.query("salary >= 400 and postcode == '300-0727'")`もDataFrameになるので
`df[["salary", "name"]]`のように列を抽出できます

``` python: PandasCsv.py
# SELECT salary, name FROM df_a WHERE salary == 1 AND postcode == 'a'
df_new = df_a[(df_a["salary"] >= 400) & (df_a["postcode"] == "300-0727")]
# または
df_new = df_a.query("salary >= 400 and postcode == '300-0727'")
print(df_new[["salary", "name"]])
```
実行結果
``` python:command line
   salary   name
3     700   川田
```


## MAX, MIN, AVG, SUM, COUNT
``` python: PandasCsv.py
# MAX 
# SELECT MAX(name), MAX(salary), MAX(postcode), MAX(birthday) FROM df_a
df_a["birthday"] = pd.to_datetime(df_a["birthday"]) # 日付は変換が必要
print(df_a[["name", "salary", "postcode", "birthday"]].max())

# MIN
# SELECT MIN(name), MIN(salary), MIN(postcode), MIN(birthday) FROM df_a
df_a["birthday"] = pd.to_datetime(df_a["birthday"]) # 日付は変換が必要
print(df_a[["name", "salary", "postcode", "birthday"]].min())

# AVG
# SELECT AVG(salary) FROM df_a
print(df_a["salary"].mean())

# SUM
# SELECT SUM(name), SUM(salary) FROM df_a
print(df_a[["name", "salary"]].sum())

# COUNT
# SELECT COUNT(name) FROM df_a
print(df_a[["name"]].count())
```
実行結果

``` python:command line
# MAX
name                         鈴木
salary                      700
postcode               300-0727
birthday    2004-01-25 00:00:00
dtype: object


# MIN
name                         佐藤
salary                      200
postcode               030-0932
birthday    1975-11-23 00:00:00
dtype: object

# AVG
383.3333333333333

# SUM
name      山田佐藤鈴木川田山田山田
salary            2300
dtype: object

# COUNT
name    6
dtype: int64
```

`df_a[["列名1", "列名2"]]`で列を指定して`max(最大値)` `min(最小値)` `mean(平均値)` `sum(合計値)` `count(個数)`をそれぞれ実行しています

[maxのパラメータ](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.max.html#pandas.DataFrame.max)
[minのパラメータ](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.min.html#pandas.DataFrame.min)
[meanのパラメータ](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.mean.html#pandas.DataFrame.mean)
[sumのパラメータ](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.sum.html#pandas.DataFrame.sum)
[countのパラメータ](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.count.html#pandas.DataFrame.count)



## GROUP BY
``` python: PandasCsv.py
# SELECT name, postcode, SUM(salary) FROM df_a GROUP BY name, postcode
print(df_a.groupby(["name", "postcode"])["salary"].sum().reset_index())
```
実行結果
``` python:command line
    name  postcode  salary
0   佐藤  300-0727     300
1   山田  030-0932     350
2   山田  038-0056     450
3   川田  300-0727     700
4   鈴木  132-0013     500
```
`groupby(列名)`でグループ化する列を指定してその後の`[列名]`を`sum(合計)`しています。また、`reset_index`で新しくインデックスを振りなおしています

[groupbyのパラメータ](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.groupby.html#pandas.DataFrame.groupby)
[reset_indexのパラメータ](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.reset_index.html#pandas.DataFrame.reset_index)



## JOIN
``` python: PandasCsv.py
# SELECT * FROM df_a LEFT JOIN df_b ON df_a.name = df_b.name
print(pd.merge(df_a, df_b, on="name", how="left"))
```
実行結果
``` python:command line
    name  salary  postcode    birthday  address
0   山田     200  038-0056    2004/1/3      青森
1   佐藤     300  300-0727   1999/6/30      茨城
2   鈴木     500  132-0013    1980/3/3      東京
3   川田     700  300-0727  1975/11/23      NaN
4   山田     250  038-0056   2004/1/25      青森
5   山田     350  030-0932   1987/9/12      青森
```
`merge`を使って二つのテーブルを結合しています
#### 使用しているパラメータ
- **on**：結合の対象にする列名(配列で渡せば複数列も可能)
- **how**：結合方法の指定(defaultでは`inner`)


[mergeのパラメータ](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.merge.html#pandas.DataFrame.merge)

`pd.merge(df_a, df_b, on="name", how="left")`これもDataFrameになりますので、さらに結合できます。

``` python: PandasCsv.py
# SELECT * FROM df_a LEFT JOIN df_b ON df_a.name = df_b.name LEFT JOIN df_a.name = df_c.name
df_ab = pd.merge(df_a, df_b, on="name", how="left")
df_abc = pd.merge(df_ab, df_c, on=["name", "salary"], how="left")
df_abc["age"] = df_abc["age"].astype(pd.Int64Dtype()) 
print(df_abc)
```
実行結果
``` python:command line
    name  salary  postcode    birthday  address   age
0   山田     200  038-0056    2004/1/3      青森    20
1   佐藤     300  300-0727   1999/6/30      茨城    25
2   鈴木     500  132-0013    1980/3/3      東京    37
3   川田     700  300-0727  1975/11/23       NaN   <NA>
4   山田     250  038-0056    2004/1/25      青森   <NA>
5   山田     350  030-0932   1987/9/12      青森    44
```

`df_abc["age"] = df_abc["age"].astype(pd.Int64Dtype())`では結合で`int`の列にNaNがあると`float`になるのNaNを扱える整数型`Int64`で変換

## AS
``` python: PandasCsv.py
# SELECT name, postcode, name + postcode AS name_postcode FROM df_a
df_a["name_postcode"] = df_a["name"] + df_a["postcode"] # 新しい列を追加
print(df_a[["name", "postcode", "name_postcode"]])

# SELECT name, salary, name + salary AS name_salary FROM df_a
df_a["name_salary"] = df_a["name"] + df_a["salary"].astype(str) # 型が違う場合は.astypeで変換する
print(df_a[["name", "salary", "name_salary"]])
```
実行結果
``` python:command line
    name  postcode  name_postcode
0   山田  038-0056    山田038-0056
1   佐藤  300-0727    佐藤300-0727
2   鈴木  132-0013    鈴木132-0013
3   川田  300-0727    川田300-0727
4   山田  038-0056    山田038-0056
5   山田  030-0932    山田030-0932

    name  salary   name_salary
0   山田     200       山田200
1   佐藤     300       佐藤300
2   鈴木     500       鈴木500
3   川田     700       川田700
4   山田     250       山田250
5   山田     350       山田350
```

AS句で表示したい列を新しくDataFrameに追加しています

## OFFSET FETCH LIMIT
``` python: PandasCsv.py
# SELECT * FROM df_a ORDER BY 何かしらの列名 OFFSET 2 ROWS FETCH NEXT 3 ROWS ONLY
# SELECT * FROM df_a LIMIT 3 OFFSET 2
print(df_a.loc[2:4])
```
実行結果
``` python:command line
    name  salary  postcode    birthday
2   鈴木     500  132-0013    1980/3/3
3   川田     700  300-0727  1975/11/23
4   山田     250  038-0056    2004/1/25
```
`loc[インデックス始まり：インデックス終わり]`で指定の行から指定の行数を取得できます

[locのパラメータ](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.loc.html#pandas.DataFrame.loc)

## INSERT
``` python: PandasCsv.py
# INSERT INTO df_a (name, salary, postcode, birthday) VALUES ("新田", 280, "682-0022", "2000/12/10")
df_insert = pd.DataFrame({"name":["新田"], "salary":[280], "postcode":["682-0022"], "birthday":["2000/12/10"] })
df_a = pd.concat([df_a, df_insert])
print(df_a.reset_index(drop=True))
```
実行結果
``` python:command line
    name  salary  postcode    birthday
0   山田     200  038-0056    2004/1/3
1   佐藤     300  300-0727   1999/6/30
2   鈴木     500  132-0013    1980/3/3
3   川田     700  300-0727  1975/11/23
4   山田     250  038-0056    2004/1/25
5   山田     350  030-0932   1987/9/12
6   新田     280  682-0022  2000/12/10
```
追加する行のDataFrameを新しく作り(`df_insert`)それを元のDataFrame(`df_a`)に足しています。また、`reset_index`で新しくインデックスを振りなおしています
#### 使用しているパラメータ
- **drop**：古いインデックスを削除するかどうかの指定

[concatのパラメータ](https://pandas.pydata.org/docs/reference/api/pandas.concat.html#pandas.concat)
[reset_indexのパラメータ](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.reset_index.html#pandas.DataFrame.reset_index)

## UPDATE
``` python: PandasCsv.py
# UPDATE df_a SET name = "上田" WHERE salary >= 250
df_a.loc[(df_a["salary"]>=500),["name"]]="上田"
print(df_a)
```
実行結果
``` python:command line
    name  salary  postcode    birthday
0   山田     200  038-0056    2004/1/3
1   佐藤     300  300-0727   1999/6/30
2   上田     500  132-0013    1980/3/3
3   上田     700  300-0727  1975/11/23
4   山田     250  038-0056    2004/1/25
5   山田     350  030-0932   1987/9/12
```
`loc[(条件式),[列名]]`を使って`df_a`の指定の値を上書きしています

[locのパラメータ](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.loc.html#pandas.DataFrame.loc)

## DELETE
``` python: PandasCsv.py
# DELETE FROM df_a WHERE name = 山田
print(df_a[df_a["name"]!="山田"])
```
実行結果
``` python:command line
    name  salary  postcode    birthday
1   佐藤     300  300-0727   1999/6/30
2   鈴木     500  132-0013    1980/3/3
3   川田     700  300-0727  1975/11/23
```
`name=山田`を削除する
↓ 言い換えれば
`name!=山田`を表示する
となりますので↑のコードでは`name!=山田`の行だけを表示しています

## 参考にさせていただいた記事、サイト

https://pandas.pydata.org/docs/index.html

https://note.nkmk.me/python-pandas-read-csv-tsv/#headercsv-header-names