ざっくりPythonのおさらい その3。Numpy, Pandas

引き続いて、numpy, pandasの基本的な使い方をざっくりと載せておきます。
本記事では行列系の演算は扱ってません。
（とは言っても、機械学習、ディープラーニングでデータを扱っていると避けては通れないので、適宜追加していきます。）


前回までのおさらいは、以下の記事をご参照ください。

[ざっくりPythonのおさらい その1。基本文法など]
https://qiita.com/48hands/items/31b7ac2b49addbb8658c

[ざっくりPythonのおさらい その2。おもにデータ保存]
https://qiita.com/48hands/items/ab86b7463268e669d216

## Numpy

`import numpy as np`で利用する。

### np.array

#### 一次元の配列

```python
import numpy as np

a1 = np.array([1, 2, 3, 4, 5])
print(a1)

# 配列サイズの確認
print(a1.shape)

# 次元数の確認
print(a1.ndim)

# 要素数の確認
print(a1.size)
```

```
[1 2 3 4 5]
(5,)
1
5
```

#### 多次元の配列

```python
import numpy as np

# 二次元の配列生成
a2 = np.array([[1, 2, 3], [4, 5, 6]])
print(a2[0])
print(a2[1])
print(a2[0][1])

# 配列サイズの確認
print(a2.shape)

# 次元数の確認
print(a2.ndim)

# 要素数の確認
print(a2.size)
```

```
[1 2 3]
[4 5 6]
2
(2, 3)
2
6
```

### np.arange

```python
# 0から9の一次元配列
a = np.arange(10)
print(a)

# 0から29まで5ずつスキップの一次元配列
a = np.arange(0, 30, 5)
print(a)

# 0から2まで0.3ずつスキップの一次元配列
a = np.arange(0, 2, 0.3)
print(a)
```

```
[0 1 2 3 4 5 6 7 8 9]
[ 0  5 10 15 20 25]
[ 0.   0.3  0.6  0.9  1.2  1.5  1.8]
```

### np.zeros / np.ones

業務処理では滅多に使うことがないかもしれませんが、行列計算でよく使います。

#### np.zeros

```python
# 3行4列の二次元配列
a = np.zeros((3, 4), dtype=np.int32)
print(a)
```

```
[[0 0 0 0]
 [0 0 0 0]
 [0 0 0 0]]
```

#### np.ones

```python
# 3行4列の二次元配列
a = np.ones((3, 4), dtype=np.int32)
print(a)
```

```
[[1 1 1 1]
 [1 1 1 1]
 [1 1 1 1]]
```

### np.reshape

`arange`関数と組み合わせて使うことが多いです。

```python
# 2 * 3の配列を作成
a = np.arange(6).reshape(2, 3)
print(a)
```

```
[[0 1 2]
 [3 4 5]]
```


```python
# 2 * 3 * 4の配列を作成
a = np.arange(24).reshape(2, 3, 4)
print(a)
```

```
[[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]

 [[12 13 14 15]
  [16 17 18 19]
  [20 21 22 23]]]
```

### np.vstack / np.hstack

以下のようなデータを扱うものとして記載しています。

```python
x = np.arange(0, 10, 2)
y = np.arange(5)
z = np.arange(0, 100, 20)

print(x)
print(y)
print(z)
```

```
[0 2 4 6 8]
[0 1 2 3 4]
[ 0 20 40 60 80]
```

#### np.vstack

垂直にスタックします。

```python
print(np.vstack([x, y, z]))
```

```
[[ 0  2  4  6  8]
 [ 0  1  2  3  4]
 [ 0 20 40 60 80]]
```

#### np.hstack

水平方向にスタックします。

```python
print(np.hstack([x, y, z]))
```

```
[ 0  2  4  6  8  0  1  2  3  4  0 20 40 60 80]
```

### np.random

```python
# 2行3列の値がランダムな数の配列を生成
a = np.random.randn(2,3)
print(a)
```

```
[[-0.00500654 -0.252814   -0.57280965]
 [-1.44175418 -0.22190351 -0.48507611]]
```

### 演算

#### 配列同士の演算

配列の要素同士の計算。(行列計算ではない。）

```python
a = np.array([[1, 2, 3, 4, 5], [3, 4, 5, 6, 7]])
b = np.array([[2, 4, 6, 7, 9], [1, 2, 4, 2, 8]])

# 要素同士の足し算
print(a + b)

# 要素同士の掛け算
print(a * b)
```


```
[[ 3  6  9 11 14]
 [ 4  6  9  8 15]]
[[ 2  8 18 28 45]
 [ 3  8 20 12 56]]
```

#### 統計的な計算

```python
a = np.array([[1, 2, 3, 4, 5], [3, 4, 5, 6, 7]])
print(a)

# すべての要素を使って計算
print(a.sum())
print(a.max())
print(a.min())
print(a.mean())
print(a.std())

# 列ごとに集計
print(a.sum(axis=0))
# 行ごとに集計
print(a.sum(axis=1))
```

```
[[1 2 3 4 5]
 [3 4 5 6 7]]

40
7
1
4.0
1.73205080757

[ 4  6  8 10 12]
[15 25]
```

## Pandas

業務処理としては、Pandasの方がNumpyよりも使う印象。(PandasもNumpyを中で使っているのでこの表現は正確ではないですが..)

Pandasによってデータをデータフレームの形式(表のようなもの)で扱えます。
`import pandas as pd`で利用する。

### データフレームの生成

#### 一次元データ

```python
import pandas as pd
import numpy as np

# 一次元
s = pd.Series([1, 2, 3, 4, np.nan, 5])
print(s)
```

```
0    1.0
1    2.0
2    3.0
3    4.0
4    NaN
5    5.0
```

左の`0,1,2,3,4,5`はインデックスを示している。


### 二次元データ

二次元の場合はよく`df`を変数名としてつかう。

#### カラムとデータをセットで与える場合

`pd.DataFrame`の引数にディクショナリ形式でデータを与える。

```python
df = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
print(df)
```

```
   A  B
0  1  3
1  2  4
```

左の`0,1`はインデックスを示している。

#### カラムとデータを別で与えたい場合

```python
df = pd.DataFrame(np.random.randn(5, 4),
                  columns=['A', 'B', 'C', 'D'])
print(df)
```

```
          A         B         C         D
0 -0.305246 -0.244308  0.960713 -1.948262
1  1.827907  2.123698  1.880136 -0.663541
2 -0.194168  1.093406  1.690319  0.609997
3 -0.530745 -0.740414  0.669239 -1.154399
4 -0.344853 -1.834740 -0.075733 -0.057627
```

さらに、インデックスを`0,1,2,...`のように定義せずに任意のものを与えたい場合は、以下のように与えることもできる。

```python
df = pd.DataFrame(np.random.randn(5, 4),
                  index=["a" + str(i) for i in range(5)],
                  columns=['A', 'B', 'C', 'D'])
print(df)
```

```
           A         B         C         D
a0  0.530294  0.322370  0.478835 -0.126045
a1  0.432960 -1.546127  0.492869  2.012067
a2 -1.603422 -0.610451  0.463467  0.199561
a3 -0.368643  1.409762 -0.185814 -0.573302
a4  0.090929  0.036672 -0.027835  0.558301
```

もしくは、pandasの`xx_range`関数が用意されている。

```python
# date_range関数を使って、20180101-20180105をインデックスとして定義
df = pd.DataFrame(np.random.randn(5, 4),
                  index=pd.date_range('20180101', periods=5),
                  columns=['A', 'B', 'C', 'D'])
print(df)
```

```
                   A         B         C         D
2018-01-01 -0.586486 -0.291565  0.473952 -0.685849
2018-01-02 -1.393820 -0.487958 -1.589376  0.105860
2018-01-03 -0.753569 -0.864522  0.662988  0.645619
2018-01-04  0.606714  0.260588  0.128833  0.432453
2018-01-05 -0.189775 -0.656559  0.161292 -0.429529
```

###  データの参照

#### head / tail / columns / values

```python
df = pd.DataFrame(np.random.randn(5, 4),
                  columns=['A', 'B', 'C', 'D'])

# 上から2行を取得
print(df.head(2))

# 下から2行を取得
print(df.tail(2))

# カラムを取得
print(df.columns)

# 値を取得
print(df.values)
```

```
          A         B         C         D
0 -0.999091 -0.122181 -0.894873  0.039718
1  0.718243 -0.133020  1.106097  0.786122

          A         B         C         D
3  1.535138 -1.018447  1.178315 -0.277484
4 -0.741541 -1.409563 -0.349262  1.506808

Index(['A', 'B', 'C', 'D'], dtype='object')

[[-0.99909119 -0.12218051 -0.89487256  0.03971803]
 [ 0.71824258 -0.13302039  1.10609694  0.78612167]
 [ 0.74734781  1.28302495 -0.25022485 -0.92677622]
 [ 1.53513828 -1.01844676  1.17831484 -0.27748411]
 [-0.74154141 -1.4095629  -0.34926238  1.50680833]]
```

なお、`head`,`tail`,`columns`,`values`の型は以下のようになっている。

```
print(type(df.head(2)))
print(type(df.tail(2)))

print(type(df.columns))
print(type(df.values))
```

```
<class 'pandas.core.frame.DataFrame'>
<class 'pandas.core.frame.DataFrame'>
<class 'pandas.core.indexes.base.Index'>
<class 'numpy.ndarray'>
```

`columns`、`values`をリスト形式にしたい場合は、以下のようにすればリストに変換できると思われる。（このやり方がいいのかはわかりませんが、どうしてもpandasのデータをリストで扱いたい場合の手段として残しておきます。）

```python
df = pd.DataFrame(np.random.randn(5, 4),
                  index=pd.date_range('20180101', periods=5),
                  columns=['A', 'B', 'C', 'D'])

print(list(df.columns))
print(list([list(a) for a in df.values]))
```

```
['A', 'B', 'C', 'D']
[[-0.002235757935339935, 1.6529973427058939, 0.64203999192301642, -1.3210444284424014], [2.3671442978642845, -1.0722592191428966, 1.2885945049511809, 0.026156012112749916], [0.77976030738001834, 0.33266376052480762, -2.5191588600551702, -0.36360286000823311], [-1.9147929973641573, 0.71150943274316547, 0.17461225929264279, -0.38409302462051303], [2.4247639005597312, 1.3167549003901313, 1.0682682650746929, 0.50789521949882876]]
```

#### 行のみを指定したアクセス

行のアクセスはインデックスを使う。

```python
# 5行4列のデータフレーム
df = pd.DataFrame(np.random.randn(5, 4),
                  index=["a" + str(i) for i in range(5)],
                  columns=['A', 'B', 'C', 'D'])

# インデックス番号を指定して参照する
print(df[0:3])

# インデックス名を指定して参照する
# df[0:3]と同じ結果になる
print(df['a0':'a2'])
```

```
           A         B         C         D
a0  0.805062 -1.033655  3.012355 -0.283463
a1  0.944916 -0.506144 -1.118409 -1.420088
a2  0.045544 -0.466361 -2.671331 -1.080808

           A         B         C         D
a0  0.805062 -1.033655  3.012355 -0.283463
a1  0.944916 -0.506144 -1.118409 -1.420088
a2  0.045544 -0.466361 -2.671331 -1.080808
```

#### 行とカラムを指定したアクセス

`loc`関数にインデックスのレンジと取得したいカラムの配列を渡す。

```python
# 5行4列のデータフレーム
df = pd.DataFrame(np.random.randn(5, 4),
                  index=["a" + str(i) for i in range(5)],
                  columns=['A', 'B', 'C', 'D'])

# a0〜a2までの行かつ、カラムA,Cのみを取得
print(df.loc['a0':'a2', ['A', 'C']])

# すべての行かつ、カラムA,Cのみを取得
print(df.loc[:, ['A', 'C']])
```

```
           A         C
a0  0.144033 -1.296219
a1  1.005402  1.443974
a2 -1.426863 -0.036709

           A         C
a0  0.144033 -1.296219
a1  1.005402  1.443974
a2 -1.426863 -0.036709
a3 -1.915530 -1.260232
a4 -0.385383 -0.243092
```

`iloc`関数で行と列の要素番号を指定したアクセスはこちら。

```python
df = pd.DataFrame(np.random.randn(5, 4),
                  index=["a" + str(i) for i in range(5)],
                  columns=['A', 'B', 'C', 'D'])

print(df.iloc[0, 0])
print(df.iloc[0:3, 0:3])
```


```
-0.196552994109

           A         B         C
a0 -0.196553  0.819213 -0.185168
a1 -0.783100 -1.275004 -0.215038
a2 -0.430817 -0.497900  0.128841
```

### カラムの追加

カラムの追加はディクショナリ形式でリストを追加するようにできる。

```python
# もとのデータフレーム（3行4列）
df = pd.DataFrame(np.random.randn(3, 4),
                  index=pd.date_range('20180101', periods=3),
                  columns=['A', 'B', 'C', 'D'])
print(df)

# カラムEを追加する
df['E'] = ['Makio', 'Shintaro', 'Hanako']
print(df)
```

```
                   A         B         C         D
2018-01-01 -0.899274 -2.721258  0.397787  0.318388
2018-01-02  0.773072 -0.091913 -0.086014  1.379136
2018-01-03 -0.203123  0.191961  1.127242  0.989349

                   A         B         C         D         E
2018-01-01 -0.899274 -2.721258  0.397787  0.318388     Makio
2018-01-02  0.773072 -0.091913 -0.086014  1.379136  Shintaro
2018-01-03 -0.203123  0.191961  1.127242  0.989349    Hanako
```

### 関数の適用

`map`もしくは`apply`に関数を渡す。

```python
df = pd.DataFrame(
    {'A': [1, 2, 3, 4], 'B': ['Hanako', 'Makio', 'Taro', 'Kanako']})

print(df)

df['A2'] = df['A'].map(lambda x: x * 2)
df['B2'] = df['B'].map(lambda x: x.startswith('H'))
df['AB'] = df.apply(lambda x: x['B'] + str(x['A']), axis=1)　# axis=1で各行に関数を適用している

print(df)
```

```
   A       B
0  1  Hanako
1  2   Makio
2  3    Taro
3  4  Kanako

   A       B  A2     B2       AB
0  1  Hanako   2   True  Hanako1
1  2   Makio   4  False   Makio2
2  3    Taro   6  False    Taro3
3  4  Kanako   8  False  Kanako4
```

上の例では`lambda`で渡しているが、以下のようにも記述できる。
`lambda`で定義する処理が長くなりそうな場合は、こちらを使うのがよさそう。

```python
df = pd.DataFrame(
    {'A': [1, 2, 3, 4], 'B': ['Hanako', 'Makio', 'Taro', 'Kanako']})

print(df)


def func1(x):
    return x * 2


def func2(x):
    return x.startswith('H')


def func3(x):
    return x['B'] + str(x['A'])


df['A2'] = df['A'].map(func1)
df['B2'] = df['B'].map(func2)
df['AB'] = df.apply(func3, axis=1)

print(df)
```

また、`assign`メソッドを使って以下のようにも記述できる。

```python
df = pd.DataFrame(
    {'A': [1, 2, 3, 4], 'B': ['Hanako', 'Makio', 'Taro', 'Kanako']})

df2 = df.assign(
    A2=lambda df: df['A'].apply(lambda x: x * 2),
    B2=lambda df: df['B'].apply(lambda x: x.startswith('H')),
    AB=lambda df: df.apply(lambda x: x['B'] + str(x['A']), axis=1))

print(df2)
```

### データの絞り込み

**where句相当**

`query`メソッドを使うのが簡単。

```python
df = pd.DataFrame(
    {'A': [-1, 0, 1, 0, -4], 'B': ['one', 'two', 'two', 'three', 'five']})

# Aが0かつBが'two'を条件として抽出
print(df.query('A == 0 and B == "two"'))
```

```
   A    B
1  0  two
```

**where in相当**

```python
df = pd.DataFrame(
    {'A': [-1, 0, 1, 0, -4], 'B': ['one', 'two', 'two', 'three', 'five']})

# B列がtwo,fourのものを抽出
df2 = df[df['B'].isin(['two', 'four'])]
print(df2)
```

```
   A    B
1  0  two
2  1  two
```

### データのソート

`sort_values`メソッドを使う。

```python
df = pd.DataFrame(
    np.array([[1, 4], [2, 7], [5, 2], [3, 3], [-1, 6], [2, 2], [3, 4]]),
    index=pd.date_range('20180101', periods=7), columns=['A', 'B'])

print(df)

# カラムA降順でソート
print(df.sort_values(by='A', ascending=False))

# カラムA降順、カラムB降順でソート
print(df.sort_values(by=['A', 'B'], ascending=[False, False]))
```


```
            A  B
2018-01-01  1  4
2018-01-02  2  7
2018-01-03  5  2
2018-01-04  3  3
2018-01-05 -1  6
2018-01-06  2  2
2018-01-07  3  4

            A  B
2018-01-03  5  2
2018-01-04  3  3
2018-01-07  3  4
2018-01-02  2  7
2018-01-06  2  2
2018-01-01  1  4
2018-01-05 -1  6

            A  B
2018-01-03  5  2
2018-01-07  3  4
2018-01-04  3  3
2018-01-02  2  7
2018-01-06  2  2
2018-01-01  1  4
2018-01-05 -1  6

```


### データの集計

`groupby`メソッドで集計できる。

```python
df = pd.DataFrame({'A': ['foo', 'bar', 'foo', 'bar'],
                   'B': np.random.randn(4)}
                  )
print(df)

# カラムAをグループ集計して合計を求める
result_df = df.groupby('A').sum()
print(result_df)
```

```
     A         B
0  foo  0.125125
1  bar  0.886186
2  foo -2.036310
3  bar -1.993052

            B
A            
bar -1.106866
foo -1.911185
```


#### 簡単に統計値を確認する

`describe`メソッドを使うと簡単に基本統計量が確認できる。

```python
df = pd.DataFrame(np.random.randn(5, 4),
                  index=pd.date_range('20180101', periods=5),
                  columns=['A', 'B', 'C', 'D'])
print(df)
print(df.describe())
```

```
           A         B         C         D
a0 -0.101431 -0.540538  0.413183 -1.112668
a1  1.139180  0.236635  1.234778  0.005053
a2  1.546940 -0.474595  0.860131 -0.058529
a3  0.339556  0.032907  0.069717 -0.638064
a4 -1.490855  1.516060 -1.045449 -0.065478

              A         B         C         D
count  5.000000  5.000000  5.000000  5.000000
mean   0.286678  0.154094  0.306472 -0.373937
std    1.186178  0.829791  0.875132  0.488301
min   -1.490855 -0.540538 -1.045449 -1.112668
25%   -0.101431 -0.474595  0.069717 -0.638064
50%    0.339556  0.032907  0.413183 -0.065478
75%    1.139180  0.236635  0.860131 -0.058529
max    1.546940  1.516060  1.234778  0.005053
```

### データフレーム同士の結合


```python
# 3行2列のデータフレームを作成
df1 = pd.DataFrame(np.random.randn(3, 2), columns=['A', 'C'])
df2 = pd.DataFrame(np.random.randn(3, 2), columns=['A', 'B'])

# 縦方向に結合
# ignore_indexでdf1,df2で振られているインデックスを無視して振り直す。
merge_df = pd.concat([df1, df2], ignore_index=True)

print(merge_df)
```

```
          A         B         C
0 -0.195391       NaN  0.597881
1 -1.412883       NaN -1.028382
2  0.010281       NaN  0.837267
3  0.351978 -0.251637       NaN
4 -0.479735 -1.370914       NaN
5  0.907136  1.774478       NaN
```


### 欠損値NaNを扱う

#### 置換する場合

前節の`merge_df`でカラムB,CのNaNを0で置換する。

```python
merge_df['B'].fillna(0, inplace=True)
merge_df['C'].fillna(0, inplace=True)
print(merge_df)
```

```
          A         B         C
0  0.496169  0.000000  2.323138
1 -0.094766  0.000000  0.250895
2 -1.200690  0.000000  0.891685
3 -0.528584 -0.358417  0.000000
4  0.706201  0.381958  0.000000
5  0.736269 -0.130781  0.000000
```


#### 削除する場合

`dropna`メソッドを利用してレコードを削除する。
オプションの違いもあるため、以下のようなデータフレームで例示しています。

```python
df = pd.DataFrame(
    {'A': [np.nan, 1, 2],
     'B': [np.nan, np.nan, 3]
     })

print(df)
```

```
     A    B
0  NaN  NaN
1  1.0  NaN
2  2.0  3.0
```

##### すべてのカラムがNaNのレコード削除

`how`に`all`を指定する。

```python
print(df.dropna(how='all'))
```

```
     A    B
1  1.0  NaN
2  2.0  3.0
```

##### いずれかのカラムがNaNのレコード削除

`how`に`any`を指定する。
(`dropna`メソッドのデフォルトなので明示的に指定しなくてもよい。)

```python
print(df.dropna(how='all'))
```

```
     A    B
2  2.0  3.0
```


### データフレームのコピー

データフレームのコピーは`copy`メソッドを使う。

```python
df1 = pd.DataFrame(
    {'A': [1, 2, 3],
     'B': [2, 2, 3]
     })


df2 = df1.copy()
df2['C'] = [5,6,7]

print(df1)
print(df2)
```


```
   A  B
0  1  2
1  2  2
2  3  3

   A  B  C
0  1  2  5
1  2  2  6
2  3  3  7
```


当たり前だが、データフレームも参照渡しなので、以下の`df1`と`df2`は同じ結果になる。

```python
df1 = pd.DataFrame(
    {'A': [1, 2, 3],
     'B': [2, 2, 3]
     })

df2 = df1
df2['C'] = [5,6,7]

print(df1)
print(df2)
```

```
   A  B  C
0  1  2  5
1  2  2  6
2  3  3  7

   A  B  C
0  1  2  5
1  2  2  6
2  3  3  7
```


### 転置

とても簡単に転置できる。

```python
df = pd.DataFrame(np.random.randn(5, 4),
                  index=pd.date_range('20180101', periods=5),
                  columns=['A', 'B', 'C', 'D'])
print(df)

# 転置
print(df.T)
```

```
                   A         B         C         D
2018-01-01  1.623736 -1.138568  0.963784  1.143847
2018-01-02  0.473417  0.937296 -0.274963  0.803029
2018-01-03 -0.628118  1.058111  0.848354 -0.563482
2018-01-04 -0.129076 -0.135657  1.400161  0.094259
2018-01-05  0.985847  1.522559 -0.094080  0.899734


   2018-01-01  2018-01-02  2018-01-03  2018-01-04  2018-01-05
A    1.623736    0.473417   -0.628118   -0.129076    0.985847
B   -1.138568    0.937296    1.058111   -0.135657    1.522559
C    0.963784   -0.274963    0.848354    1.400161   -0.094080
D    1.143847    0.803029   -0.563482    0.094259    0.899734
```

### 外部データの読み書き

#### テキストデータの読み書き

##### 読み込み

`pd.read_csv`で読み込む。

```hoge.csv
name,age
hanako,22
taro,25
```

```python
df = pd.read_csv('hoge.csv', sep=',', encoding='utf-8')
# ヘッダーがない場合には、header=Noneを指定する。
# df = pd.read_csv('hoge.csv', sep=',', header=None,encoding='utf-8')

print(df)
```


```
     name  age
0  hanako   22
1    taro   25
```

##### 書き込み

```python
import csv

# データフレームのインデックスはファイル出力させたくなかったので
# index=Falseを指定している。
# quoting=csv.QUOTE_ALLの部分は、quoting=1としても結果は同じ
df.to_csv('target.csv',
          sep=',',
          index=False,
          encoding='utf-8',
          header=True,
          quoting=csv.QUOTE_ALL)
```

```target.csv
"name","age"
"hanako","22"
"taro","25"
```

#### RDBに対する読み書き

##### 読み込み

`pd.read_sql`を使ってデータを読み込む。

**SQLAlchemyを使ってengineを引数に渡すVer**

`engine`を渡すだけなので、コネクション管理しなくてもよいのかと思う。

```python
import sqlalchemy

# mysql+pymysqlを指定する
url = 'mysql+pymysql://username:password@127.0.0.1/my_db'
engine = sqlalchemy.create_engine(url, echo=True)

df = pd.read_sql('select * from persons', engine)
print(df)
```

```
    id      name
0    1   Robot-1
1    2   Robot-2
2    3   Robot-3
```

**SQLAlchemyを使わずにコネクションを渡すVer**

```python
import mysql.connector

conn = mysql.connector.connect(host='127.0.0.1',
                               port=3306,
                               user='root',
                               database='my_db')

df = pd.read_sql('select * from persons', conn)
print(df)
```

```
    id      name
0    1   Robot-1
1    2   Robot-2
2    3   Robot-3
```

##### 書き込み

`df.to_sql`で実行すればよいが、引数`con`にはSQLAlchemyの`engine`を指定しないと実行できなかった。

```python
url = 'mysql+pymysql://username:password@127.0.0.1/my_db'
engine = sqlalchemy.create_engine(url, echo=True)

df = pd.read_csv('robot.csv')

# 第一引数にテーブル名を指定。
# conにはengineを指定
# index=FalseにしてDataFrameのインデックスは無効化した。
# if_existsをappendとしてテーブルにデータを新規に追加する
df.to_sql('persons', 
          con=engine, 
          index=False, 
          if_exists='append')
```

**[注意事項]**

ここで、`if_exists`は、`'append'`のほか、`'fail'`,`'replace'`を指定できるが、`'fail'`はテーブルが存在していたら`ValueError`になる。

`'replae'`を指定すると、`drop table`してからデータをインサートするので注意が必要。


また、用意したデータ(robot.csv)はこちら。

```robot.csv
id,name
100,Robot-101
101,Robot-102
102,Robot-103
```

`echo=True`で`engine`を生成しているので、SQL実行ログも出力してくれる。

```
2018-09-02 19:11:02,923 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'sql_mode'
2018-09-02 19:11:02,923 INFO sqlalchemy.engine.base.Engine {}
2018-09-02 19:11:02,926 INFO sqlalchemy.engine.base.Engine SELECT DATABASE()
2018-09-02 19:11:02,926 INFO sqlalchemy.engine.base.Engine {}
2018-09-02 19:11:02,927 INFO sqlalchemy.engine.base.Engine show collation where `Charset` = 'utf8mb4' and `Collation` = 'utf8mb4_bin'
2018-09-02 19:11:02,927 INFO sqlalchemy.engine.base.Engine {}
2018-09-02 19:11:02,928 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS CHAR(60)) AS anon_1
2018-09-02 19:11:02,928 INFO sqlalchemy.engine.base.Engine {}
2018-09-02 19:11:02,929 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS CHAR(60)) AS anon_1
2018-09-02 19:11:02,929 INFO sqlalchemy.engine.base.Engine {}
2018-09-02 19:11:02,929 INFO sqlalchemy.engine.base.Engine SELECT CAST('test collated returns' AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_bin AS anon_1
2018-09-02 19:11:02,929 INFO sqlalchemy.engine.base.Engine {}
2018-09-02 19:11:02,930 INFO sqlalchemy.engine.base.Engine DESCRIBE `persons`
2018-09-02 19:11:02,930 INFO sqlalchemy.engine.base.Engine {}
2018-09-02 19:11:02,932 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2018-09-02 19:11:02,932 INFO sqlalchemy.engine.base.Engine INSERT INTO persons (id, name) VALUES (%(id)s, %(name)s)
2018-09-02 19:11:02,932 INFO sqlalchemy.engine.base.Engine ({'id': 100, 'name': 'Robot-101'}, {'id': 101, 'name': 'Robot-102'}, {'id': 102, 'name': 'Robot-103'})
2018-09-02 19:11:02,936 INFO sqlalchemy.engine.base.Engine COMMIT
```