Python Pandas を Excel代わりに利用してデータ操作

# Pandas での データ操作

最近は利用するデータも巨大化しているため、Excelで開くのも大変です。

結構 Excelでは開けない、もしくは開くと Excelが重すぎて操作できないデータが結構あります。

Python の Pandas ライブラリを活用してExcelでのデータ操作を代替してみたいと思います。

Pandas の導入やファイル読み込みに関してはこの文書では説明しませんので、以下のリンク先を参照してください。
- [Pandas の導入とデータ型 - Qiita](https://qiita.com/xza/items/88264d9ad4c14ff40215)
- [Pandas で様々なファイルを読み込み操作する - Qiita](https://qiita.com/xza/items/8bdcf3f62ce2ae2e6c21)

# データ取得先

機械学習が認知されてきたせいか、オープンデータが多くなってきています。

東京都もいくつかのデータを公開しています。

[東京都オープンデータカタログサイトホームページ](http://opendata-portal.metro.tokyo.jp/www/index.html)


ちょと東京都の公開するExcelはデータ処理目的だと不便なのでCSVを加工してExcelにしたりして操作してみます。

サンプルとして東京都の人工推移CSVを利用してみます。以下のリンク先から「CSV」を取得してください。

http://opendata-catalogue.metro.tokyo.jp/dataset/t000003d0000000001/resource/2232b9a1-228b-41e4-afbc-bcfae5e52f62

# ファイル読み込み

pandas の import から開始します。


```python
import pandas as pd
```

この時点では CSV は一度も開いた事が無い状態だとすると、文字コードが不明です。
文字コード判定は Python の「chardet」ライブラリを利用します。


```python
import chardet

with open('data/tn13qa020100.csv', mode='rb') as f:
    # 1行だけ読み込み
    file_line = f.readline()
    # 判定
    print(chardet.detect(file_line))
```

```
    {'encoding': 'SHIFT_JIS', 'confidence': 0.99, 'language': 'Japanese'}
```

取得したCSVの文字コードがSJISだとわかりました。

pandas で SJISを読み込む時はencodingの指定が必要です。

政府系のファイルでSJISの場合、Windowsで作成されたファイルの可能性が高いので、ここでは一応「cp932」で読み込んでおきます。


```python
df = pd.read_csv('data/tn13qa020100.csv', encoding='cp932')
df
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>年次（Ｙｅａｒ）</th>
      <th>人口（Ｐｏｐｕｌａｔｉｏｎ）</th>
      <th>人口増減率（Ｒａｔｅ ｏｆ ｐｏｐｕｌａｔｉｏｎ ｃｈａｎｇｅ）（％）</th>
      <th>対全国比（％Ｎａｔ’ｌ ｐｏｐｕｌａｔｉｏｎ）（％）</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>明治５年（１８７２）</td>
      <td>859345</td>
      <td>…</td>
      <td>2.47</td>
    </tr>
    <tr>
      <th>1</th>
      <td>６（１８７３）</td>
      <td>887322</td>
      <td>3.26</td>
      <td>2.54</td>
    </tr>
    <tr>
      <th>2</th>
      <td>７（１８７４）</td>
      <td>932458</td>
      <td>5.09</td>
      <td>2.65</td>
    </tr>
    <tr>
      <th>3</th>
      <td>８（１８７５）</td>
      <td>986091</td>
      <td>5.75</td>
      <td>2.79</td>
    </tr>
    <tr>
      <th>4</th>
      <td>９（１８７６）</td>
      <td>1027517</td>
      <td>4.2</td>
      <td>2.89</td>
    </tr>
    <tr>
      <th>5</th>
      <td>１０（１８７７）</td>
      <td>890681</td>
      <td>-13.32</td>
      <td>2.48</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
    </tr>
    <tr>
      <th>138</th>
      <td>２２（２０１０）</td>
      <td>＊13159388</td>
      <td>0.63</td>
      <td>10.28</td>
    </tr>
    <tr>
      <th>139</th>
      <td>２３（２０１１）</td>
      <td>13186562</td>
      <td>0.21</td>
      <td>10.32</td>
    </tr>
    <tr>
      <th>140</th>
      <td>２４（２０１２）</td>
      <td>13216221</td>
      <td>0.22</td>
      <td>10.36</td>
    </tr>
    <tr>
      <th>141</th>
      <td>２５（２０１３）</td>
      <td>13286735</td>
      <td>0.53</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>142</th>
      <td>平成23年からは，平成22年国勢調査を基準とした10月１日現在の推計人口である。Figure...</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>143</th>
      <td>*印は国勢調査人口（10月１日現在）Figures marked with the *  r...</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>144</th>
      <td>注 1) 市部寄留者の数に重複があったための減である。 (Note: 1)   Reduct...</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>145</th>
      <td>2) 北多摩、南多摩及び西多摩郡が神奈川県から東京府へ移ったための増である。(2) Incr...</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>146</th>
      <td>資料：都総務局統計部人口統計課 「人口の動き-「東京都の人口（推計)」年報-」Source:...</td>
      <td>NaN</td>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
  </tbody>
</table>
<p>147 rows × 4 columns</p>
</div>



データを見ると末尾5行は説明文だとわかります。この行は処理上不要なので、削除します。


```python
df = df[:-5]
df.tail()
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>年次（Ｙｅａｒ）</th>
      <th>人口（Ｐｏｐｕｌａｔｉｏｎ）</th>
      <th>人口増減率（Ｒａｔｅ ｏｆ ｐｏｐｕｌａｔｉｏｎ ｃｈａｎｇｅ）（％）</th>
      <th>対全国比（％Ｎａｔ’ｌ ｐｏｐｕｌａｔｉｏｎ）（％）</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>137</th>
      <td>２１（２００９）</td>
      <td>13077625</td>
      <td>0.86</td>
      <td>10.21</td>
    </tr>
    <tr>
      <th>138</th>
      <td>２２（２０１０）</td>
      <td>＊13159388</td>
      <td>0.63</td>
      <td>10.28</td>
    </tr>
    <tr>
      <th>139</th>
      <td>２３（２０１１）</td>
      <td>13186562</td>
      <td>0.21</td>
      <td>10.32</td>
    </tr>
    <tr>
      <th>140</th>
      <td>２４（２０１２）</td>
      <td>13216221</td>
      <td>0.22</td>
      <td>10.36</td>
    </tr>
    <tr>
      <th>141</th>
      <td>２５（２０１３）</td>
      <td>13286735</td>
      <td>0.53</td>
      <td>NaN</td>
    </tr>
  </tbody>
</table>
</div>



データ情報確認をします。


```python
df.info()
```

```
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 142 entries, 0 to 141
    Data columns (total 4 columns):
    年次（Ｙｅａｒ）                               142 non-null object
    人口（Ｐｏｐｕｌａｔｉｏｎ）                         142 non-null object
    人口増減率（Ｒａｔｅ ｏｆ ｐｏｐｕｌａｔｉｏｎ ｃｈａｎｇｅ）（％）    142 non-null object
    対全国比（％Ｎａｔ’ｌ ｐｏｐｕｌａｔｉｏｎ）（％）             141 non-null float64
    dtypes: float64(1), object(3)
    memory usage: 4.5+ KB
```


列名が日本語全角だとプログラム的に扱いずらいので、rename します。


```python
df.columns = ['year_orig', 'pop', 'rate', '%']
df.head()
```




<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>year_orig</th>
      <th>pop</th>
      <th>rate</th>
      <th>%</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>明治５年（１８７２）</td>
      <td>859345</td>
      <td>…</td>
      <td>2.47</td>
    </tr>
    <tr>
      <th>1</th>
      <td>６（１８７３）</td>
      <td>887322</td>
      <td>3.26</td>
      <td>2.54</td>
    </tr>
    <tr>
      <th>2</th>
      <td>７（１８７４）</td>
      <td>932458</td>
      <td>5.09</td>
      <td>2.65</td>
    </tr>
    <tr>
      <th>3</th>
      <td>８（１８７５）</td>
      <td>986091</td>
      <td>5.75</td>
      <td>2.79</td>
    </tr>
    <tr>
      <th>4</th>
      <td>９（１８７６）</td>
      <td>1027517</td>
      <td>4.2</td>
      <td>2.89</td>
    </tr>
  </tbody>
</table>
</div>

年 は元号と西暦が混在していると処理しずらいので、分割します。

```python
def conv_year(row):
    # 後ろ5文字から1文字が西暦
    year = int(row[-5:-1])
    
    # 前半
    wareki = row[:-6]
    
    gengo = ''
    if len(wareki) >= 3:
        # 3文字以上の時、元号が含まれている
        gengo = wareki[:2]
 
        wareki = wareki[2:]
        
        if '年' in wareki:
            wareki = wareki[:-1]
         
        if '元' == wareki:
            wareki = 1
        
    wareki = int(wareki)
    
    return year, gengo, wareki
    
df['year'], df['元号'], df['和暦'] = zip(*df['year_orig'].map(conv_year))

# 元号は空になる物があるので、後で処理
gengo_lst = []

now_gengo = ''
for gengo in df['元号'].tolist():
    if gengo == '':
        gengo = now_gengo
    else:
        now_gengo = gengo
    gengo_lst.append(gengo)
    

df['元号'] = gengo_lst

```

上記の書き方はメモリ効率があまり良くない書き方のため、warningが出ます。今回の場合は特に問題ありません。

```
    ipykernel_launcher.py:26: SettingWithCopyWarning: 
    A value is trying to be set on a copy of a slice from a DataFrame.
    Try using .loc[row_indexer,col_indexer] = value instead
    
    See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
    
    ipykernel_launcher.py:40: SettingWithCopyWarning: 
    A value is trying to be set on a copy of a slice from a DataFrame.
    Try using .loc[row_indexer,col_indexer] = value instead
    
    See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
```


変換されたか確認します。


```python
df.head()
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>year_orig</th>
      <th>pop</th>
      <th>rate</th>
      <th>%</th>
      <th>year</th>
      <th>元号</th>
      <th>和暦</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>明治５年（１８７２）</td>
      <td>859345</td>
      <td>…</td>
      <td>2.47</td>
      <td>1872</td>
      <td>明治</td>
      <td>5</td>
    </tr>
    <tr>
      <th>1</th>
      <td>６（１８７３）</td>
      <td>887322</td>
      <td>3.26</td>
      <td>2.54</td>
      <td>1873</td>
      <td>明治</td>
      <td>6</td>
    </tr>
    <tr>
      <th>2</th>
      <td>７（１８７４）</td>
      <td>932458</td>
      <td>5.09</td>
      <td>2.65</td>
      <td>1874</td>
      <td>明治</td>
      <td>7</td>
    </tr>
    <tr>
      <th>3</th>
      <td>８（１８７５）</td>
      <td>986091</td>
      <td>5.75</td>
      <td>2.79</td>
      <td>1875</td>
      <td>明治</td>
      <td>8</td>
    </tr>
    <tr>
      <th>4</th>
      <td>９（１８７６）</td>
      <td>1027517</td>
      <td>4.2</td>
      <td>2.89</td>
      <td>1876</td>
      <td>明治</td>
      <td>9</td>
    </tr>
  </tbody>
</table>
</div>



次に人口の型を変換します。「*」等が入っているため、数字ではありません。


```python
df['pop'][:10]
```

```
    0     859345
    1     887322
    2     932458
    3     986091
    4    1027517
    5     890681
    6    1072560
    7    1084025
    8    1084745
    9    1121560
    Name: pop, dtype: object
```


人口を数字に変換します。


```python
def conv_pop(val):
    # 変換関数
    if '＊' in val:
        val = val[1:]
    elif '）' in val:
        val = val[2:]
        
    return int(val)

df['pop']  = df['pop'].map(conv_pop)

df['pop']
```

```
    0        859345
    1        887322
    2        932458
    3        986091
    4       1027517
    5        890681
             ...   
    137    13077625
    138    13159388
    139    13186562
    140    13216221
    141    13286735
    Name: pop, Length: 142, dtype: int64
```


rate も同様に変換します。


```python
def conv_rate(val):
    if '… ' == val:
        return 0.0

    return float(val)

df['rate'] = df['rate'].map(conv_rate)

df['rate']
```

```
    0       0.00
    1       3.26
    2       5.09
    3       5.75
    4       4.20
    5     -13.32
           ...  
    137     0.86
    138     0.63
    139     0.21
    140     0.22
    141     0.53
    Name: rate, Length: 142, dtype: float64
```


型が整形されたか、再度 info で確認してみます。


```python
df.info()
```

```
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 142 entries, 0 to 141
    Data columns (total 7 columns):
    year_orig    142 non-null object
    pop          142 non-null int64
    rate         142 non-null float64
    %            141 non-null float64
    year         142 non-null int64
    元号           142 non-null object
    和暦           142 non-null int64
    dtypes: float64(2), int64(3), object(2)
    memory usage: 7.8+ KB
```


```python
df.head()
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>year_orig</th>
      <th>pop</th>
      <th>rate</th>
      <th>%</th>
      <th>year</th>
      <th>元号</th>
      <th>和暦</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>明治５年（１８７２）</td>
      <td>859345</td>
      <td>0.00</td>
      <td>2.47</td>
      <td>1872</td>
      <td>明治</td>
      <td>5</td>
    </tr>
    <tr>
      <th>1</th>
      <td>６（１８７３）</td>
      <td>887322</td>
      <td>3.26</td>
      <td>2.54</td>
      <td>1873</td>
      <td>明治</td>
      <td>6</td>
    </tr>
    <tr>
      <th>2</th>
      <td>７（１８７４）</td>
      <td>932458</td>
      <td>5.09</td>
      <td>2.65</td>
      <td>1874</td>
      <td>明治</td>
      <td>7</td>
    </tr>
    <tr>
      <th>3</th>
      <td>８（１８７５）</td>
      <td>986091</td>
      <td>5.75</td>
      <td>2.79</td>
      <td>1875</td>
      <td>明治</td>
      <td>8</td>
    </tr>
    <tr>
      <th>4</th>
      <td>９（１８７６）</td>
      <td>1027517</td>
      <td>4.20</td>
      <td>2.89</td>
      <td>1876</td>
      <td>明治</td>
      <td>9</td>
    </tr>
  </tbody>
</table>
</div>



整形済みデータを一旦保存します。

Excelとpicleに保存しておきます。


```python
df.to_pickle('data/df.pickle')
df.to_excel('data/df.xlsx')
```

# pickle から処理開始

pickle を利用すればそこから処理を再開できます。


```python
df = pd.read_pickle('data/df.pickle')

df.info()
```

```
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 142 entries, 0 to 141
    Data columns (total 7 columns):
    year_orig    142 non-null object
    pop          142 non-null int64
    rate         142 non-null float64
    %            141 non-null float64
    year         142 non-null int64
    元号           142 non-null object
    和暦           142 non-null int64
    dtypes: float64(2), int64(3), object(2)
    memory usage: 7.8+ KB
```

取得した値に問題ないか、とりあえずグラフを書いて確認してみます。


```python
%matplotlib inline
import seaborn as sns
sns.set_style('ticks')
sns.set_context('talk')

sns.pointplot(x='year', y='pop', data=df[-10:])
```

```
    <matplotlib.axes._subplots.AxesSubplot at 0x10b7142b0>
```


![output_31_1.png](https://qiita-image-store.s3.amazonaws.com/0/203673/04885de8-35d8-0604-8490-1dee00954bb0.png)

「pickle」ファイルを保存しておくと、処理が途中から再開できますので、便利です。

# グルーピング

SQLで言う「group by」を実施してみます。

グループする場合、列名等を指定します。「keys」を利用して、列名を確認しておきます。


```python
df.keys()
```

```
    Index(['year_orig', 'pop', 'rate', '%', 'year', '元号', '和暦'], dtype='object')
```


とりあえず「元号」を利用してみます。

「unique()」を利用して、いくつのグループになるか事前に確認しておきます。


```python
df['元号'].unique()
```

```
    array(['明治', '大正', '昭和', '平成'], dtype=object)
```


panasのグルーピングは「groupby」を利用します。

https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.groupby.html


```python
for key, group in df.groupby('元号'):
    print(key, len(group))
```

```
    大正 15
    平成 25
    明治 40
    昭和 62
```

上記では ループさせて、len で数を確認していますが「size」を利用すればループの必要はありません。


```python
grup = df.groupby('元号')
grup.size()
```

```
    元号
    大正    15
    平成    25
    明治    40
    昭和    62
    dtype: int64
```


「平均(mean)」、「最小値(min)」、「最大値(max)」、「合計(sum)」が利用できます。


```python
grup.mean()
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>pop</th>
      <th>rate</th>
      <th>%</th>
      <th>year</th>
      <th>和暦</th>
    </tr>
    <tr>
      <th>元号</th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>大正</th>
      <td>3.462471e+06</td>
      <td>3.770000</td>
      <td>6.203333</td>
      <td>1919.0</td>
      <td>8.0</td>
    </tr>
    <tr>
      <th>平成</th>
      <td>1.235242e+07</td>
      <td>0.443200</td>
      <td>9.727917</td>
      <td>2001.0</td>
      <td>13.0</td>
    </tr>
    <tr>
      <th>明治</th>
      <td>1.597809e+06</td>
      <td>3.051000</td>
      <td>3.792500</td>
      <td>1891.5</td>
      <td>24.5</td>
    </tr>
    <tr>
      <th>昭和</th>
      <td>8.799917e+06</td>
      <td>1.948387</td>
      <td>9.469516</td>
      <td>1957.5</td>
      <td>32.5</td>
    </tr>
  </tbody>
</table>
</div>




```python
grup.min()
```


<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>year_orig</th>
      <th>pop</th>
      <th>rate</th>
      <th>%</th>
      <th>year</th>
      <th>和暦</th>
    </tr>
    <tr>
      <th>元号</th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>大正</th>
      <td>大正元年（１９１２）</td>
      <td>2757500</td>
      <td>-3.13</td>
      <td>5.33</td>
      <td>1912</td>
      <td>1</td>
    </tr>
    <tr>
      <th>平成</th>
      <td>平成元年（１９８９）</td>
      <td>11773605</td>
      <td>-0.44</td>
      <td>9.37</td>
      <td>1989</td>
      <td>1</td>
    </tr>
    <tr>
      <th>明治</th>
      <td>明治５年（１８７２）</td>
      <td>859345</td>
      <td>-13.32</td>
      <td>2.47</td>
      <td>1872</td>
      <td>5</td>
    </tr>
    <tr>
      <th>昭和</th>
      <td>昭和２年（１９２７）</td>
      <td>3488284</td>
      <td>-52.02</td>
      <td>4.83</td>
      <td>1927</td>
      <td>2</td>
    </tr>
  </tbody>
</table>
</div>




```python
grup.max()
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>year_orig</th>
      <th>pop</th>
      <th>rate</th>
      <th>%</th>
      <th>year</th>
      <th>和暦</th>
    </tr>
    <tr>
      <th>元号</th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>大正</th>
      <td>９（１９２０）</td>
      <td>4694400</td>
      <td>14.47</td>
      <td>7.73</td>
      <td>1926</td>
      <td>15</td>
    </tr>
    <tr>
      <th>平成</th>
      <td>９（１９９７）</td>
      <td>13286735</td>
      <td>1.06</td>
      <td>10.36</td>
      <td>2013</td>
      <td>25</td>
    </tr>
    <tr>
      <th>明治</th>
      <td>９（１８７６）</td>
      <td>2732000</td>
      <td>20.42</td>
      <td>5.52</td>
      <td>1911</td>
      <td>44</td>
    </tr>
    <tr>
      <th>昭和</th>
      <td>９（１９３４）</td>
      <td>11904896</td>
      <td>19.92</td>
      <td>11.10</td>
      <td>1988</td>
      <td>63</td>
    </tr>
  </tbody>
</table>
</div>




```python
grup.sum()
```

<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>pop</th>
      <th>rate</th>
      <th>%</th>
      <th>year</th>
      <th>和暦</th>
    </tr>
    <tr>
      <th>元号</th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>大正</th>
      <td>51937072</td>
      <td>56.55</td>
      <td>93.05</td>
      <td>28785</td>
      <td>120</td>
    </tr>
    <tr>
      <th>平成</th>
      <td>308810484</td>
      <td>11.08</td>
      <td>233.47</td>
      <td>50025</td>
      <td>325</td>
    </tr>
    <tr>
      <th>明治</th>
      <td>63912378</td>
      <td>122.04</td>
      <td>151.70</td>
      <td>75660</td>
      <td>980</td>
    </tr>
    <tr>
      <th>昭和</th>
      <td>545594878</td>
      <td>120.80</td>
      <td>587.11</td>
      <td>121365</td>
      <td>2015</td>
    </tr>
  </tbody>
</table>
</div>

# Pandas の様々な機能練習
以下のリンク先等を参照すると、様々なパターンを実習できます。
- https://pandas.pydata.org/pandas-docs/stable/tutorials.html
- http://nbviewer.jupyter.org/github/guipsamora/pandas_exercises/tree/master/


# 書いた人に関して
[Tech Fun株式会社](http://www.techfun.co.jp/)スペシャリスト、xza です。

社内で開催した初学者向け勉強会で利用した資料等を公開しています。