Python PandasのSeries、Dataframeの基本操作（１）

pythonでデータ分析するとき、pandasというモジュールを使うのが一般的である（らしい）。

pandasにおいて、データはSeriesやDataframeという型に収納できる。
Seriesは一次元、Dataframeは二次元のデータを収納するために使われる。
それぞれ、高機能の一次元の配列、二次元の配列のようなものである。
高機能というのは、横行や縦列にそれぞれ名前が付けられる他、多くのメソッドが用意されているのだ。

|#|ハチマン|ユキノ|ユイ|
|:-:|:-:|:-:|:-:|
|**数学**|  8| 90| 10|
|**国語**| 88|100| 50|
|**英語**| 38| 95| 35|
>二次元配列でこれを表現するとき、「ハチマン」「ユキノ」「ユイ」や「数学」「国語」「英語」といった文字の扱いに困る。Dataframeではこれをcolumnsやindexで表せる。

しかし、この型はいろいろと厄介な仕様があり、私はのっけから躓いてしまった。
これはpandas超初心者の私が自分用に作った、超初歩的なpandas操作マニュアルである。
pythonのバージョンは3.5.2（標準pythonではなく、Anacondan4.2.0を使っている）
pandasのバージョンは0.18.1
コードはiPython5.1.0で実行している状況をイメージしている。

#pandasを使う前の準備
##pandasのインストール
私はAnacondaで一括で入れた。（Anacondaはpython+よく使われるライブラリみたいなものである。NumpyやiPythonも入れられる）
そのほかにも、pipやなんやで入れてもいい。

##pandasのインポート
pandasはモジュールであるため、インポートしなければならない。
`In[1]: import pandas`
しかし、どこの参考サイトを見ても、pandasはpdという名前で読み込まれているようであるから、ここでもそれに倣う。
`In[2]: import pandas as pd`

#Seriesの操作
##Seriesを作る
|#|ハチマン|
|:-:|:-:|:-:|:-:|
|**数学**|  8| 90| 10|
|**国語**| 88|100| 50|
|**英語**| 38| 95| 35|
例えば、こんな一次元の配列が与えられたとしよう。
まず、これを見て思いつくのは、Listを作ることである。

```In[3]: HachimanList = [8, 88, 38]```

これの要素にアクセスするのは簡単だ。
国語の点数がほしいのであれば、
`In[4]: Hachiman[1]`とすれば
`Out[4]: 88`と返ってくるだろう。

これの問題点は、「数学」の点数、といった情報が抜けてしまうことだ。
もちろん、辞書を作ったり、オブジェクトを作ったり、名前付きタプルを作ったりしてもいいが、いずれも大規模データ処理には向かない趣がある。

これを解決するのが、pandasのSeriesである。

```
In[5]: HachimanSeries = pd.Series(HachimanList, index = ["math", "japanese", "english"])
```

このように、

```
変数 = pd.Series(データの配列, index = 横行の名前の配列)
```

で指定できる。
これを出力してみると、

```
In[6]: HachimanSeries
Out[6]: 
math         8
japanese    88
english     38
dtype: int64`
```

とそれぞれの項目に名前を与えられて、出力されているのがわかる。
なお`dtype`は配列全体のデータ型である。（Numpyで整数型はいくつかの種類の異なる整数型に割り振られる。int64はその内の一つである）

indexを指定していなかったらどうなるのだろうか？エラーが返ってくるのだろうか？

```
In[7]: YukinoSeries = pd.Series([90, 100, 95]) 
In[8]: YukinoSeries
Out[8]: 
0     90
1    100
2     95
dtype: int64
```
どうやら、`index`は0から増えていくデフォルト値が設定されているようだ。
なお、`index`は後から加えることができる。

```
In[9]: YukinoSeries.index = ["math", "japanese", "english"]
In[10]: YukinoSeries
Out[10]: 
math         90
japanese    100
english      95
dtype: int64
```

辞書を用いる方法もある。

```
In[11]: YuiSeries = pd.Series({"math":10, "japanese":50, "english":35})
In[12]: YuiSeries
Out[12]: 
english     35
japanese    50
math        10
dtype: int64
```
この場合、順番がバラバラになってしまうのは仕方ない。

##Seriesの値を取り出す
Series内の値を取り出すのは通常の配列と殆ど操作が変わらない。
####要素の指定
配列のn番目の要素は`配列[n-1]`で取り出す。
同様に、Seriesのn番目の要素は

```
In[13]: HachimanSeries[2]
Out[14]: 38
```
また、これを変数に渡して、計算することも可能だ。

```
In[15]: HachimanMath = HachimanSeries[0]
In[16]: 40 <= HachimanMath
Out[16]: False
```

ただし、HachimanMathの型は普通の`int`ではなく、`numpy.int64`である。


```
In[17]: type(HachimanMath)
Out[17]: numpy.int64
```

`配列[-1]`も使える。
複数の変数を取り出す事も簡単だ。

```
In[18]: HachimanSeries[0:2]
Out[18]: 
[8, 38]
```

私の実行結果を鵜呑みのせず、自分自身の環境でサンプルコードを実行してきた読者なら、ここで私がとうとう馬脚を現して、うそを書いたことに気が付いたはずだ。

このコード`HachimanSeries[0:2]`は`pandas.core.series.Series`という見るからにSeries型の実行結果を返してくる。

```
In[18]: HachimanSeries[0:2]
Out[18]: 
math         8
japanese    88
dtype: int64

In[19]: type(HachimanSeries[0:2])
Out[19]: pandas.core.series.Series
```

まとめると

- 要素を単独で指定すると、`numpy.int64`という`int`型の様な実行結果が
- 要素を範囲で指定すると`pandas.core.series.Series`という`Series`型と言うべきものが返ってくる

ちょっと気持ち悪いと感じる人もいるかも知れないが、よくよく考えると

- `int`->`numpy.int64`
- `list` ->`pandas.core.series.Series`

という対応関係があるだけで、ごく普通の配列の操作と変わらない。
だからもちろん、単独の要素を持つ`Series`型は、配列から単独の配列を取り出すときと同様に取り出せる。

```
In[20]: HachimanSeries[1:1+1] 
Out[20]: 
japanese    88
dtype: int64

In[21]: type(HachimanSeries[1:1+1])
Out[21]: 
pandas.core.series.Series
```

要素を指定するには、辞書型のように`index`名で指定する方法もある。

```
In[22]: HachimanSeries["math"]
Out[22]: 8
```

ちなみに`配列[::-1]`を使うと、逆にした結果が返ってくる。

驚くべきことに、これは`index`名を使って、範囲指定までできる。

```
In[23]: HachimanSeries["math":"english"]
Out[23]: 
math         8
japanese    88
english     38
dtype: int64
```

これは、普通の辞書はもちろん、collections.OrderedDictでもできないことであり、Seriesたんの高性能を示している。

なお、`index`の名前にアクセスしたいときは、`Series.index`を配列のように扱えばいい。


```
In[24]:HachimanSeries.index
Out[24]:Index(['math', 'japanese', 'english'], dtype='object')

In[25]: HachimanSeries.index[1]
Out[25]: 'japanese'

In[26]: HachimanSeries.index.[1:2]
Out[26]: Index(['japanese'], dtype='object')
```


ここまで、Seriesが普通の配列や辞書の様に要素を取り出せることを説明してきた。
####ほしい形のSeriesを手に入れる
`Series`のなかでデータのつまみ食いをしたい場合はどうすればいいだろうか？
つまり、`math`と`japanese`だけが欲しいとか、`math`と`english`だけが欲しいとか。
あるいは、`math`が二か所に欲しいとか。
（今はそんな必要性を感じないか…）

`math`と`japanese`なら、`HachimanSeries[0:2]`でなんとかなる。しかし、`math`と`english`となると、なかなか悩ましい。
そこで思いつく。

```
In[27]: HachimanSeries["math"]+HachimanSeries["english"]
```
これでどうだ！！

```
Out[27]: 46
```

現実は非情であるが、この出力はうなずける。そもそも、`HachimanSeries["math"]`の結果は`numpy.int64`なのだから。

ならばと、

```
In[28]: HachimanSeries[0:0 + 1] + HachimanSeries[2:2 + 1]
```

を試してみる。

```
Out[28]: 
english   NaN
math       NaN
dtype: float64
```

見るからに産業廃棄物を吐いてきた。
これはおそらく、`Series`における足し算が、「同じindex同士を足す」ものだからであろう。
そして、共通しない要素に関しては、とりあえず`NaN`を埋めておくのだ。
現に

```
In[29]: HachimanSeries[0:0 + 2] + YukinoSeries[1:1 + 2]
Out[29]: 
english       NaN
japanese    188.0
math          NaN
dtype: float64
```
となる。

では、同じSeries内の`math`と`english`だけを贔屓するにはどうしたらいいだろうか。
答えは、二重に`[]`を書く、である。

```
In[30]: HachimanSeries[[0, 2]]
Out[30]:
math        8
english    38
dtype: int64
```

たぶん、この`[[]]`は二次配列の表記と関係ない。ただ単に`[[]]`という表記が使いたかっただけなのだと思われる。
（`HachimanSeries[(0,2)]`は通らないため、イテレーター的な何かである必要はなかったのだろう。しかし、`HachimanSeries[list((0,2))]`は通るため、処理的には配列と同じであると考えられる。）

ひたすらに数学の得点を強調したければ、

```
In[31]: HachimanSeries[["math","math","math","math","math"]]
Out[31]: 
math    8
math    8
math    8
math    8
math    8
dtype: int64
```

とすればいい。（ここでは`index`名を直接指定した）
`index`に関しても同様だ。

```
In [32]: HachimanSeries.index[[1,2]]
Out[32]: Index(['japanese', 'english'], dtype='object')
```

ここまで、`[[]]`を用いて、欲しい`index`だけを抽出したSeriesを新しく作る方法を学んだ。
###Seriesの要素の書き換え
後から`Series`の中身や`index`名が間違っていたことに気が付くかもしれない。
同じ名前で修正版`Series`を上書きする方法もあるが、実は、配列と同じように簡単に変えられる。

まずは一個だけ書き換えるコード。

```
In[33]:HachimanSeries[1]
Out[33]: 88

In[34]: HachimanSeries[1] = 98
 
In[35]: HachimanSeries[1]
Out[35]: 98 
```


次に指定した範囲を書き換える

```
In[36]: HachimanSeries
Out[36]: 
math         8
japanese    98
english     38
dtype: int64

In[37]: HachimanSeries[1:1+2] = [89,33]

In[38]: HachimanSeries
Out[38]: 
math         8
japanese    89
english     33
dtype: int64
```

ここで、左辺と右辺の個数があってないと怒られる。


```
In[39]: HachimanSeries[1:1+1] = [88,38]
-------------------
ValueError
（略）
ValueError: cannot set using a slice indexer with a different length than the value
```

ただし、同じ値に揃える事は出来る。

```
In[40]: HachimanSeries[0:0+3] = 0

In[41]: HachimanSeries
Out[40]: 
math        0
japanese    0
english     0
dtype: int64
```

最後に、`index`の書き換え

```
In[42]: HachimanSeries.index[1] = "Japanese"
Out[42]: HachimanSeries
math        0
Japanese    0
english     0
dtype: int64
```

と実はこういかない。

```
TypeError: Index does not support mutable operations
```
とあるように、`index`はイミュータブルであるようだ。（文字列で似たことをしても怒られる）

だから上書きするしかない。

```
In[43]: HachimanSeries.index = ["Math","Japanese","English"]

In[44]: HachimanSeries
Out[44]: 
Math        0
Japanese    0
English     0
dtype: int64
```

さて、復習がてらリセットしておこう。

```
In[45]: HachimanSeries[0:0+3] = [8,88,38]

In[46]: HachimanSeries.index = ["math", "japanese", "english"]
Out[46]: 
math         8
japanese    88
english     38
dtype: int64
```

以上、Seriesの基本的な操作である。
予想以上に長くなってしまったため、Dataframeについてや、Seriesのメソッドについては続く記事で記す。