pandas勉強　その１（自己学習用）

分析のためにpythonを習得しているので、具体的な集計・分析手法や前処理の方法を学んできたが
ここらでよく使うpandasについてちゃんと勉強してみよう、と思いメモ。
知っている人には当たり前なんだろうけど、どこかふわふわした理解のままではスッキリしないので。

参考文献はこちら
[Pythonによるデータ分析入門](https://www.amazon.co.jp/dp/487311845X)

参考文献を見ながら、自分の手でコード書いて実行。
重要な箇所を見て気付いたことを少し書く、というスタイルですので無駄に長いです。。。
※引用箇所が参考文献の本文です。


## シリーズ（Series）
よく出てきて混乱を起こしたやつ。わかったようなわからないような。
Numpyのarrayとの違いがよくわからなかった。

### インデックスについて
>シリーズは１次元の配列のようなオブジェクトです
>連続した値と、それに関連付けられた**インデックス**というデータラベルの配列が含まれます

おお、なるほど。知らなかった、インデックスがセットになってくるのが大事なのか。

```Python
x = pd.Series([1,2,3,4])
x

＜結果＞
0    1
1    2
2    3
3    4

```
１、２、３、４はシリーズのデータで、０、１、２、３というのがインデックスなんですね。
これはインデックスを指定しなかったからこうなるようでインデックスを指定することもできる。

```Python
x = pd.Series([1,2,3,4], index = ['a','b','c','d'])
x

＜結果＞
a    1
b    2
c    3
d    4

```
インデックスを指定したのでちゃんと変わっています。

>Numpyの配列と違って、１つの値や複数の値を参照するときにインデックスのラベルを使って指定することができます。

そういえばそんなことしていたな、と改めて納得。道場で基礎を磨くのはやっぱり悪くない。

```Python
x['b']

＜結果＞
2
```

### 計算
>条件指定によるフィルタリング、スカラー値の掛け算、数学的な関数の適用、などのNumpyの関数
>やNumpy風の操作を行った場合も、インデックスとデータ値との関連は保持されます。

```Python
x[x > 2]

＜結果＞
c    3
d    4
```

```Python
x * 2

＜結果＞
a    2
b    4
c    6
d    8
```

```Python
np.exp(x)

＜結果＞
a     2.718282
b     7.389056
c    20.085537
d    54.598150
```

計算されて値は変わってるけどインデックスは変わってないですね。理解。

>シリーズをインデックスとデータ値がマッピングされた固定長の順序つきディクショナリととらえ
>る見方もあります。ディクショナリを使う多くの文脈では、シリーズを使うことができるでしょう。

```Python
'b' in x

＜結果＞
True
```

```Python
'e' in x

＜結果＞
False
```

### いろいろな作成方法
>pythonのディクショナリ形式のデータがある場合は、それを使ってシリーズを作成することができます。

```Python
y = {'a': 100, 'b': 200, 'c': 300, 'd': 400}
z = pd.Series(y)
z

＜結果＞
a    100
b    200
c    300
d    400
```

ここまでやって、エクセルのINDEX関数で参照していたのが、今作っているインデックスなのかなと気付く。理解。
今作ったシリーズに対して

```Python
w = ['a','b','C','D']
t = pd.Series(y, index = w)
t

＜結果＞
a    100.0
b    200.0
C      NaN
D      NaN
```
インデックスを付け替えようとすると、元のインデックスと対応しているデータはそのままデータ値で、
そうでない箇所（ここでは大文字のCとD）は、NaNになっています。

>NaNはpandasでは欠損値、または、NA値として扱われます。
>また'c'と'd'は、指定したwには含まれていないため、作成されたシリーズからは除外されています。

そんな処理になっていたのか。これも知らなかった。

>pandasの**isnull関数**と**notnull関数**は欠損値を特定するために使います。

```Python
pd.isnull(t)

＜結果＞
a    False
b    False
C     True
D     True
```

```Python
pd.notnull(t)

＜結果＞
a     True
b     True
C    False
D    False
```

>シリーズはこれらの関数をインスタンスメソッドとしても持っています

```Python
t.isnull()

＜結果＞
a    False
b    False
C     True
D     True
```

>算術演算をするときに別々のインデックス付けされたデータが自動的に整形される、というものがあります。

```Python
z

＜結果＞
a    100
b    200
c    300
d    400
```

```Python
t

＜結果＞
a    100.0
b    200.0
C      NaN
D      NaN
```

```Python
z + t

＜結果＞
C      NaN
D      NaN
a    200.0
b    400.0
c      NaN
d      NaN
```

>シリーズのインデックスは代入して置き換えることができます。

```Python
t.index = ['1','2','3','4']
t

＜結果＞
1    100.0
2    200.0
3      NaN
4      NaN
```
ちゃんとインデックスが変わっています。

## データフレーム（DataFrame）
これもわかるようなわからないようなの代表。

>データフレームはテーブル形式のデータ構造を持ち、順序づけられた列を持っています。各列には
>別々の型（数値型、文字列型、ブール型など）を持たせることができます。データフレームは行と列の
>両方にインデックスを持っています

１列の中では同じ型です、ということかな、どのようにデータを持っているかが大事なのか。
行方向にも列方向にもインデックスがある→インデックスの組み合わせでデータを特定できそう。

>データフレームはシリーズをバリューとしても持つディクショナリと見ることができます。
>（各シリーズのインデックスを全体で共有しているようなイメージです）

ちょっとイメージが湧かないので、今はスルーします。

### 作成方法
>一般的な方法は同じ長さを持つリスト型のバリューを持ったディクショナリか、Numpyの配列を使う方法です。

```Python
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'],
        'year': [2000, 2001, 2002, 2001, 2002, 2003],
        'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}

frame = pd.DataFrame(data)
frame

＜結果＞
	state	year	pop
0	Ohio	2000	1.5
1	Ohio	2001	1.7
2	Ohio	2002	3.6
3	Nevada	2001	2.4
4	Nevada	2002	2.9
5	Nevada	2003	3.2
```
### 操作
#### 列の並べ替え
>列の順番を指定すると、データフレームの列はその順番で並びます。

```Python
pd.DataFrame(data, columns=['year', 'state', 'pop'])

＜結果＞
	year	state	pop
0	2000	Ohio	1.5
1	2001	Ohio	1.7
2	2002	Ohio	3.6
3	2001	Nevada	2.4
4	2002	Nevada	2.9
5	2003	Nevada	3.2
```
左から指定した順番に並んでいることがわかります。

#### データの無い列の指定
>指定した列がデータを持っていない場合、その列は結果として欠損値が代入されます

```Python
frame2 = pd.DataFrame(data, columns=['year', 'state', 'pop', 'debt'],
                      index = ['one','two','three','four','five','six'])

frame2

＜結果＞
	    year	state	pop	 debt
one	    2000	Ohio	1.5	  NaN
two	    2001	Ohio	1.7	  NaN
three	2002	Ohio	3.6	  NaN
four	2001	Nevada	2.4	  NaN
five	2002	Nevada	2.9	  NaN
six	    2003	Nevada	3.2	  NaN
```

もともとなかった’debt’には欠損値のNaNが代入されています。

#### データの取り出し
>データフレームの列はディクショナリ風の参照や、属性指定をすることで、シリーズとして
>取り出すことができます。

```Python
frame2['state']

＜結果＞
one        Ohio
two        Ohio
three      Ohio
four     Nevada
five     Nevada
six      Nevada
```

>取り出したシリーズはデータフレームの持っていたインデックスと同じインデックスを持ち、name属性も適切に設定されています。
>行も一夜名前で参照することができます。名前で参照するときには、**loc**という属性を使います。

```Python
frame2.loc['three']

＜結果＞
year     2002
state    Ohio
pop       3.6
debt      NaN
```
ここでは'three'という名前で参照しているので、'three'行のデータがカラム名と共に表示されています。

#### データの代入
>列の値は代入して変更できます。

```Python
frame2['debt'] = 16.5
frame2

＜結果＞
	    year	state	pop	 debt
one	    2000	Ohio	1.5	  16.5
two	    2001	Ohio	1.7	  16.5
three	2002	Ohio	3.6	  16.5
four	2001	Nevada	2.4	  16.5
five	2002	Nevada	2.9	  16.5
six	    2003	Nevada	3.2	  16.5
```

先ほどはNaN値であった'debt'に16.5が代入されているのがわかります。

```Python
frame2['debt'] = np.arange(6.)
frame2

＜結果＞
	    year	state	pop	 debt
one	    2000	Ohio	1.5	  0.0
two	    2001	Ohio	1.7	  1.0
three	2002	Ohio	3.6	  2.0
four	2001	Nevada	2.4	  3.0
five	2002	Nevada	2.9	  4.0
six	    2003	Nevada	3.2	  5.0
```

列の長さが一致していれば、np.arangeを列指定で代入することもできるんですね。
なんとなくやっていたことの理解が深まる。

>シリーズを列に代入する場合は、ラベルはデータフレームのインデックスに従って正確に一致
>するように代入が行われ、データフレームのインデックスに対応するものがない場合は、欠損値が
>挿入されます。

```Python
val = pd.Series([-1.2, -1.5, -1.7], index = ['two', 'four', 'five'])
frame2['debt'] = val
frame2

＜結果＞
	    year	state	pop	 debt
one	    2000	Ohio	1.5	  Nan
two	    2001	Ohio	1.7	  -1.2
three	2002	Ohio	3.6	  NaN
four	2001	Nevada	2.4	  -1.5
five	2002	Nevada	2.9	  -1.7
six	    2003	Nevada	3.2	  NaN
```

おお、確かにインデックスが一致しているのでその箇所だけ代入されています。

#### 存在しない列への代入（列の生成）、列の削除
>存在しない列に代入を行うと、新しい列が作成されます。
>delキーワードを使うと、ディクショナリと同じように列を消すことができます。

```Python
frame2['eastern'] = frame2.state == 'Ohio'
frame2

＜結果＞
	    year	state	pop	 debt	 eastern
one	    2000	Ohio	1.5	  NaN	 True
two	    2001	Ohio	1.7	 -1.2	 True
three	2002	Ohio	3.6	  NaN	 True
four	2001	Nevada	2.4	 -1.5	 False
five	2002	Nevada	2.9	 -1.7	 False
six	    2003	Nevada	3.2	  NaN	 False
```

もともとなかった'eastern'に代入しようとすると、新しく列が生成され、代入したいデータが入っています。
列の追加は確かにこんな風にやってた。仕組みがわかると面白い。

```Python
del frame2['eastern']
frame2

＜結果＞
	    year	state	pop	 debt	 
one	    2000	Ohio	1.5	  NaN	
two	    2001	Ohio	1.7	 -1.2	
three	2002	Ohio	3.6	  NaN	 
four	2001	Nevada	2.4	 -1.5	 
five	2002	Nevada	2.9	 -1.7	 
six	    2003	Nevada	3.2	  NaN	 
```
ちゃんと消えていますね。

#### ネストしたディクショナリ
>ネストしたディクショナリをデータフレームに渡すと、pandasは外側のディクショナリのキーを
>列のインデックスとして解釈し、内側のインデックスのキーを行のインデックスとして解釈します。

```Python
pop = {'Nevata': {2001: 2.4, 2002: 2.9},
       'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}}

frame3 = pd.DataFrame(pop)
frame3

＜結果＞
	 Nevata	 Ohio
2001	2.4	  1.7
2002	2.9	  3.6
2000	NaN	  1.5 
```

外側のディクショナリのキー　＝　'Nevada'、'Ohio'の２つですね。これが列のインデックスになっています。
内側のインデックスのキー　＝　'2000'、'2002'と言った年データです。これが行のインデックスになっています。

#### 行列の入れ替え
>データフレームはNumpyの配列と同様な文法で転置することができます。

```Python
frame3.T

＜結果＞
	    2001  2002	2000
Nevata 	 2.4   2.9	 NaN
Ohio	 1.7   3.6	 1.5 
``` 

行と列が入れ替わっているのがわかります。

#### value参照
>value属性を参照すると、データフレームの中のデータが２次元のndarrayとして戻されます。

```Python
frame3.values

＜結果＞
array([[2.4, 1.7],
       [2.9, 3.6],
       [nan, 1.5]])
``` 

## インデックスオブジェクト
>pandasのインデックスオブジェクトは、軸のラベルやその他のメタデータ（軸のname属性やnames属性など）を
保持する役目を持っています。シリーズやデータフレームを初期化するときに、配列やシーケンスなどで指定した
ラベルは、内部的にはインデックスオブジェクトに変換されます。

```Python
obj = pd.Series(range(3), index = ['a','b', 'c'])
index = obj.index
index

＜結果＞
Index(['a', 'b', 'c'], dtype='object')
``` 

説明のまんまですね。インデックスがオブジェクトになっています。

>インデックスオブジェクトは配列と似ているだけでなく、固定長のセットしても機能します。

```Python
frame3.columns

＜結果＞
Index(['Nevata', 'Ohio'], dtype='object')
```

```Python
'Ohio' in frame3.columns

＜結果＞
True
```



## まとめ

この本ですが、最初読んだとき意味不明で泣きそうでしたが、pythonの実際の操作をやってみて
少し慣れた今見るととても理解が進みました。
とりあえず手を動かしてみる、その後しっかり理解する、という順番で学んでいくの良さそうです。