すごいPandasさっくり学ぼう

# すごいPandasさっくり学ぼう

# はじめに

`Pandas`とはプログラミング言語`Python`において、データ解析を支援する機能を提供するライブラリです[^wiki]。`Pandas`は`Python`のライブラリでも複雑だと思います[^atm]。 しかし、その分、自由度が非常に高く、データ分析やさんにとって、`Pandas`なしでのデータ分析は考えられません。そこで、「ここまで理解しとけば、あとは(他サイトとかみたら)どうにでもなるわ」ってところまで説明したいと思います[^title]。

[^wiki]: https://ja.wikipedia.org/wiki/Pandas 参照

[^atm]: でも難しいって言えない雰囲気がある。

[^title]:  `Pandas`は言語ではないんだけど、題名がしっくりきたので。

## 攻略方法

\1. 準備


+ `numpy`(1次元)のインデックス参照、スライシング、ブールインデックス参照、ファンシーインデックス参照を使えるようにする

+ `numpy`(2次元)のインデックス参照、スライシング、ブールインデックス参照を使えるようにする。`numpy.iloc_`関数の挙動を理解する。(ファンシーインデックス参照は2次元でのndarrayでは使いづらい仕様になっているので、個人的にはほとんど使わない)


\2. `Pandas`入門

+ `Series, DataFrame`の作成、インデックス参照を使えるようにする(`Series`は基本的に`numpy`(1次元)の拡張。`DataFrame`は基本的に`df.loc`(ラベル名優先)または`dx.iloc`(番号優先)を用いれば基本的に`numpy`(2次元)の拡張。)
    
+ `Series, DataFrame`のデータ追加、抽出、削除、修正などをできるようにする
    
+ (`Series, DataFrame`内の要素やindex名が文字列なら、抽出や修正の一括操作が可能。便利なので、`Pandas`の`str`アクセサによる文字列処理を理解する)



ぐらいまでやっておくと、あとは軌道に乗って自分でいろいろ調べられるレベルに到達するはず(`groupby`とかも[1]とかでスムーズに理解できるはず)。

+ 全体を通して言えるのが、特に演算後のdimension(次元)がいくつなの？ということをコードを書く際に常に意識することが大事だと思います。

例えば、`Numpy`なら、

```python
arr = np.arange(12) # arrは1次元のndarray
arr = arr.reshape(3,4) # arrは2次元のndarrary
# arr[i,j]の第一要素は行、第2要素は列
arr[:2] # 2次元のndarray
arr[:2, 0] # 1次元のndarray
arr[:, arr[0] > 2] #2次元のndarray
```

`Pandas`なら、

```python
pop = {'Nevada' : {2001 : 2.4, 2002 : 2.9},
       'Ohio' : {2000 : 1.5, 2001 : 1.7}}
df = DataFrame(pop) # DataFrame(2次元)
df[df['Nevada'] > 2] # DataFrame(2次元)
df.iloc[-1:]['Nevada'] # Series(1次元)
```

というような感じで、型は何なん？ってことを意識し理解すれば、半分は終わったようなもんです。


----

というわけで、ndarray(2次元)のインデックス参照の挙動をまとめてから、`Pandas`に進むことにしましょ〜



# 準備

## import

```python
import numpy as np # ndarray
# matplot をjupyterで表示するのに必要
%matplotlib inline
import matplotlib.pyplot as plt
from pandas import Series, DataFrame
import pandas as pd
```


## Numpy

2次元のndarrayを取り上げます。`Pandas`を理解するためにここで理解することは、以下の二つです:

+ `Numpy`のインデックス参照、スライシング、ブールインデックス参照、ファンシーインデックス参照をきちんと理解している

+ 2次元の`Numpy`は`arr[<行指定>]`, `arr[<行指定>, <列指定>]`である(こんがらがらずに理解できている)。



### 例

```python
arr = np.arange(12).reshape(3,4) # arrは2次元のndarrary(3行4列)
#array([[ 0,  1,  2,  3],
#       [ 4,  5,  6,  7],
#       [ 8,  9, 10, 11]])
```

+ indexerの引数が1つだけのときは以下のような感じになります：

```python
# 1次元のndarrayを得る
arr[1] # スカラー値で要素参照
arr[0:2] # スライシング 0行目と1行目を抽出する(2行目は抽出されない)
## 一行目の各要素に対して、(>2)の真偽値を返す
arr[1] > 2 # array([ True,  True,  True,  True], dtype=bool) 

# 2次元のndarrayを得る
arr>2 # ブールインデックス参照
arr[np.array([True, False, True])] # ブールインデックス参照で0行目と2行目を抽出する
# arr[[True, False, True]] # Warning
arr[[0,2,1]] # ファンシーインデックス参照 : インデックス参照に(整数)配列を用いる 0行目と2行目と1行目を順序付きで抽出する
```

+ `Numpy`のindexer(2次元なら `arr[･(第一引数), ･(第二引数)]`)とします。第一引数が行、第二引数が列にあたります。

基本的には1次元のndarrayと同じなのですが、陥りやすい落とし穴だけメモします：

```python
# 第二引数のみを指定したい時。第一引数は省略できない。その時はスライシング`:`を第一引数としておく
arr[:, 1]

# 第一引数、第二引数にファンシーインデックスを指定すると、ちょっと直感的でない動作をする。
## (1次元のndarrayになってしまうことも注意すること！
## np.array([arr[i,j] for i,j in zip([1,2], [0,1])])と同等。 # array([4, 9])
arr[[1,2], [0,1]]
## 1,2行目かつ0,1列目の領域を抽出するような2次元のndarrayを取得するには、次のようにやる
arr[np.iloc_([1,2], [0,1])]
array([[4, 5],
       [8, 9]])
```

+ `Numpy`自体でこういう書き方をする機会は限りなく低いのですが、`Pandas`を使用する際に考え方が重要になってくるので、以下に記述しておきます(とりあえず飛ばしてOK)：

```python
# 行
arr[:,1] > 2 # array([False,  True,  True], dtype=bool)

# 1列目が(>2)であるような行を抽出する
arr[arr[:,1] > 2] # arr[np.array([False,  True,  True])]と同じ。(個人的にはあんまり使ったことない)
# arr[arr[:, 1] > 2, :]と同じ。


arr[1] > 5
arr[:, arr[1] > 5] # array([False, False,  True,  True], dtype=bool)
#arr[:, np.array([False, False, True, True])] # と同じ
```


まとめると、ndarray(2次元)のインデックス参照の型の挙動は以下のような感じです[^summary]：

[^summary]: やや強引ですけど。第二引数の「なし」の列は`arr[･]`をさしてます。(1d)の丸括弧とかは、あんまり使わないことを意味します。1dは1次元の`ndarray`、2dは2次元の`ndarray`を表します。

第一引数\第二引数|なし|スカラー|スライシング|ブールインデックス|ファンシーインデックス
---|---|---|---|---|---|---|---|---|---|---|---
なし|-|❌|❌|❌|❌|
スカラー|1d|0d|1d|1d|1d|
スライシング|2d|2d|2d|2d|2d|
ブールインデックス|2d|1d|2d|(2d)|(1d)|
ファンシーインデックス|2d|1d|2d|(1d)|(1d)|


+ 他には、普通の`list`との挙動の違いは押さえておきたい(微妙に違う)

```python
# 以外と間違えてしまう罠。arrの要素を3倍にしたい
> arr = [0,1,2,3]
> arr*4
[0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]

> np.arange(4)*4
[0,4,8,12]
# numpyに変換せずに同様のことを行うならば、内包表記で。
> [i*4 for i in range(4)]
[0,4,8,12]
```


# Pandas入門編

`Numpy`では、1次元であろうが2次元であろうが、同じndarrayとして扱えばよかったんですが、`Pandas`では、1次元 => `Series`, 2次元 => `DataFrame`として分かれています。なので、名前は違えど、2次元 <=> 1次元の行き来をするため、`DataFrame`と`Series`を切り離すことはできません。

例えば、`DataFrame`から単一行/列を指定すれば、1次元である`Series`を抜き出すことができます。逆に`Series`(1次元)の`list`or`dict`を`DataFrame`(2次元)のコンストラクタの引数に指定することで`DataFrame`を作成することができます。

ということで、変数の次元が1次元なのか2次元なのかを理解することは`Series, DataFrame`と名前が変わっても重要になるわけです。

## Seriesについて

### Seriesの作成

基本的に、コンストラクタに`dict`, `list`を突っ込むことが多いです。`dict`の場合は、index付きの`Series`になります。

```python
# dictを突っ込む例
dic = {'word' : 470, 'camera' : 78}
Series(dic)
# zipとdictの合わせ技でSeriesを生成することもよくあります：
Series(dict(zip(words, frequency)))
```

### インデックス参照

インデックス参照については、1次元のndarrayの拡張です。違いとしては、index名もインデックスの引数として入れることができる点です。

```python
ser = Series(np.random.randn(5), index = list('ABCDE'))
#A    1.700973
#B    1.061330
#C    0.695804
#D   -0.435989
#E   -0.332942
#dtype: float64

# スライシング
ser[1] # 1行目、つまり'A'行を抽出 0次元(type = float64型)　
ser['A'] # 'A'行を抽出(type = float)
ser[1:3] #1,2行目を抽出 (Series(一次元)
ser[-1:] # 最後の行を抽出
ser[:-1] # 最後の行を除く全ての行を抽出
ser[[1,2]] # 1,2行目を抽出(ファンシーインデックス)
ser[['A', 'B']] # (ファンシー)インデックスを文字列で与えることもできる
ser > 0 # serの型はSeries(1次元)で各要素は真偽値 
ser[ser > 0] # ブールインデックス(ser > 0)で要素参照

# Read, Write両方できるので、以下のように、該当するもののみに右辺値を書き込むこともできる.
# 左辺値に条件を持っていくテクニックはDataFrameでもよく使う。
ser[ser > 0] = 0
```

## DataFrameについて

### DataFrameの作成

+ 2次元のものをコンストラクタの引数に突っ込むわけですけど、外側が`list` or `dict`ならば、内側はほとんど何でも構わない(`list`でも`Series`でも`dict`でも`tuple`でも可)

```python
# 外側も内側も　dictの場合
pop = {'Nevada' : {2001 : 2.4, 2002 : 2.9},
       'Ohio' : {2000 : 1.5, 2001 : 1.7}}
df2 = DataFrame(pop)
#      Nevada  Ohio
#2000     NaN   1.5
#2001     2.4   1.7
#2002     2.9   NaN

# 外側がdict, 内側がseriesの場合 
# df1, df2はDataFrame型 (なので、df1['name'], df2['address']はSeries型です)
## column名は['typeA', 'typeB'], index名は[0,1,2,3]
dfA = DataFrame({'typeA' : df1['name'], 'typeB' : df2['address']})
## index名は[0,1,2,3], column名は['name', 'address'] (attributeのTは転置)
dfB = DataFrame([df1['name'], df2['address']]).T
```

+ builtinの`zip`関数を使って、`DataFrame`を作ることもよくあります：

```python
dict(zip([1,2,3], [4,5,6,7])) #{1: 4, 2: 5, 3: 6} => DataFrame化できない
list(zip([1,2,3], [4,5,6,7])) #[(1, 4), (2, 5), (3, 6)] => DataFrame化できる(外側:リスト、内側:タプルなので)
pd.DataFrame(list(zip([1,2,3], [4,5,6,7]))) # => OK!
```

+ 2次元ndarrayを突っ込んでも`DataFrame`の作成が可能です。

```python
df = DataFrame(np.arange(12).reshape(3,4), columns = list('ABCD'))
print(df)
   A  B   C   D
0  0  1   2   3
1  4  5   6   7
2  8  9  10  11
```

+ `Series`との合わせ技で`DataFrame`を作成することも。

```python
DataFrame(Series({'word' : 470, 'camera' : 78}), columns = ['frequency'])
```

`Series`からの`DataFrame`作成は、初心者編のデータ追加の節でも詳しく見ていきます。


### DataFrameのインデックス参照


`Pandas`では`df[･]` or`df.loc[<行指定>]`or,`df.loc[<行指定>, <列指定>]`or `df.iloc[<行指定>]` or `df.iloc[<行指定>, <列指定>]`ができます。`df[･]`は以下のように結構紛らわしい挙動をします。

```python
# よく使う
#dfA[1] # runtime error!! 一列目を整数値で取り出せない
#dfA['typeA'] #'typeA'列をSeries(1次元)として抽出
dfA[['typeB', 'typeA']] # typeB, typeA列を順序込みでDataFrame(2次元)として抽出
dfA['typeA'] > 3 # 1次元のSeries (各要素は真偽値)


# ちょっとややこしい(が個人的にはよく使う)
dfA[dfA['typeA'] > 3] # dfAの'typeA'列が3以上である行を抽出。
# dfA.loc[dfA['typeA'] > 3] #不安なら こちらを使うべし


# 以下、かなりややこしいので、あまり使わない。
dfA[1:] # 1行目~をDataFrame(2次元)として抽出(行の抽出であることに注意)
#dfA[1:]よりも自分ならこう書く。
dfA.loc[1:] # 行指定であることを明示。もしくは、dfA.loc[1:, :]
```

`df.loc`は`Numpy`のラベル名指定もOKなバージョンです。ということで、基本的に、`Numpy`のインデックス参照と同じノリで書けばよろしい.

### `df.loc`の注意事項

ただし、`df.loc`を扱う際の注意事項としては、2つあります(結構重要かつはまりやすい)。

一つは、`df.loc`はラベル名優先なので、`index`に整数値が指定されていた時もインデックスの番号が参照されるのではなく、ラベル名に対応する行が抽出される点です。例えば`sort`して1番目までの行を抽出したい時とかは、結構事故になりやすいです：

```python
dic = list(zip([0,3,5,6], list('ADCB')))
dfA = DataFrame(dic, columns = ['typeA', 'typeB'])
#   typeA typeB
#0      0     A
#1      3     D
#2      5     C
#3      6     B
dfA = dfA.sort_values(by = 'typeB')
#   typeA typeB
#0      0     A
#3      6     B
#2      5     C
#1      3     D
dfA.loc[1] #1番目の(つまり2位の)行を抽出したいが、locを用いると、indexのラベル名1の行が抽出される：
#typeA    3
#typeB    D
#Name: 1, dtype: object


## こういう悲惨なことを防ぐには、df.ilocを用いる。行番号が優先される。
##(#3      6     B)が抜き出せる
dfA.iloc[1]
```

`iloc`は抽出後にもよく使います.(`index`が番号順で無くなっている場合は`loc[番号]`で参照できない)

```python
df = df[df['A'] == name]
df.iloc[0]['B'] # ちょっとすっきりしない感じはするが...
```


もう一つは、これも整数値インデックスを扱う場合に陥りやすい罠ですが、最終行を抜き出したいときは、`df.loc`に負の値を要素参照すると失敗します。ラベル名優先なので、-1のラベルなんてないよ〜って言われるわけです。この場合も`df.iloc`を用いて、行番号に関する抽出であることを強調することで解決します。

```python
# dfA.loc[-1] : NG
dfA.iloc[-1] # OK(最終行がSeries(1次元)として抽出される)
dfA.iloc[-1:] # OK(最終行がDataFrame(2次元)として抽出される)
```

逆に`df.iloc`は番号しか使えないので、行は行番号で、列はラベル名で指定したい時は、以下のように書くと良い。

```python
df.iloc[i]['A'] # こう書くのが良い
# ilocは列指定は数字しかできない
# Location based indexing can only have
# [integer, integer slice (START point is INCLUDED, END point is EXCLUDED), listlike of integers, boolean array] types
# res *= df.iloc[i, 'A'] #error
```

-----

`DataFrame`のインデックス参照についてまとめると、

+ 不安なら、`df[･]`よりも`df.loc[<行指定>]`or,`df.loc[<行指定>, <列指定>]`を用いる。

+ ただし、行番号の抽出を強調したい場合は、`df.iloc[<行指定>]` or `df.iloc[<行指定>, <列指定>]`を用いる。

の2点だけ頭に入れとけば、`Numpy`のインデックス参照と同じように楽しいデータ抽出ができます。本当は`df.loc`の一種類のみ覚えれば良いなら気楽なんですが、整数値インデックスは実用上非常によく用いられるので、`df.iloc`の使用を避けるわけにはいかないのですよ :sweat:


## 補足

`loc`, `iloc`は以下のようにindexerを2重に使うとコピー <= 左辺値に置けない.(もとのDataFrameの修正したい値が修正されない)

```python
#A value is trying to be set on a copy of a slice from a DataFrame
df.loc[5]['colA'] # 左辺値にはできません

# 問題なし！ (参照なので)
df.loc[k, 'non_view_rate'] *= mult
```

-----

# Pandas初心者編

ここまでは`Pandas`のインデックス参照について見ていきました。たぶん山場は超えたのですが、`DataFrame`の追加とか修正とかそこらへんのややこしい部分が残っております。各関数の基本的な使い方については、 [Pythonによるデータ分析入門-―NumPy、pandasを使ったデータ処理](https://www.amazon.co.jp/Pythonによるデータ分析入門-―NumPy、pandasを使ったデータ処理-Wes-McKinney/dp/4873116554) にお任せして、ここでは、逆引き的な感じにまとめていきたい。


## Pandasを用いたで〜たの整形方法


### 追加(連結)

```python
ser = Series([1,2,3], index = list('ABC'))
#A    1
#B    2
#C    3
#dtype: int64
```

を`Series(3*1)`と表現することにします。index名は全て同じとします。(`['A', 'B', 'C']`) いろいろなパターンのデータの連結方法を見ていきます。

#### (Series(3*1) <- Series(3*1)) -> DataFrame

+ `DataFrame(2*3)`にしたい

```python
DataFrame([s1, s2]) #コンストラクタをもちいて
```


+ `DataFrame(3*2)`にしたい

```python
df = DataFrame([s1, s2], index = list('AB')).T
pd.concat([s1, s2], axis = 1) # 下方向に積み上げたいときはconcat(.., axis = 1)を用いれば良い
```

+ `DataFrame(6*1)`にしたい

```python
serA.append(serB)
# または、
pd.concat([serA, serB])
# インデックス名を0の連番にしたければ..
s1.append(s2).reset_index(drop = True) # インデックスを再振り分け
```

+ `DataFrame(1*6)`にしたい

```python
df1 = DataFrame(serA)
df2 = DataFrame(serB)
ndf = df1.join(df2, how = 'outer', lsuffix = 'A', rsuffix = 'B') #かなぁ
# こっちは二つしか連結できないけれど。
ndf = pd.merge(df1, df2, left_index=True, right_index=True, how='outer') 
   0A  1A  2A  0B  1B  2B
0   1   2   3   4   5   6
```

#### (DataFrame(n*3) <- Series(3*1)) => DataFrame

+ `DataFrame((n+1)*3)`にしたい

```python
# Can only append a Series if ignore_index=True or if the Series has a index name
df.append(serA, ignore_index = True)

cols = ['colA', 'colB', 'colC']
res_df = DataFrame(columns = cols)
res_df = res_df.append(Series([1,2,3], cols).T, ignore_index = True)
...
```


#### 複数のSeriesを積み重ねたい

+ こんなかんじにしたい

`(Series(3*1) + Series(3*1)) + Series(3*1) + Series(3*1)-> DataFrame(4*3)`

```python
df = DataFrame([serA, serB, serC, serD])
# DataFrame(3*4)にしたい場合は.Tをつければ良い
df = DataFrame([serA, serB, serC, serD]).T
```

#### 既存のDataFrameに最終行(Series)を下方向に追加したい(または下方向にDataFrameを追加する)


+ http://pythondatascience.plavox.info/pandas/pandasのデータフレームに行や列を追加する/ を参照。

```python
# 1行追加
df.loc['newrow'] = 0
df.append(serA, ignore_index = True)
# 複数行を追加
df1.append(df2)
#　複数のDataFrameを追加(リストを突っ込める)
df1.append([df2, df3, df4])
# または、
pd.concat([df1, df2, df3, df4])

# 1列追加
df['newcol'] = 0
```

#### indexがほぼ共通していて,dfを右方向につなげたい

```python
# 該当しないindexは外部結合(outer)にする。(NAN値にする。)
df1.join(df2, how = 'outer')
df1.join([df2, df3], how = 'outer')
# mergeはもっと細やかなことができるが、2つのDataFrameの結合に限定される：
df1.merge(df1, how = 'outer')
```



#### その他の追加

+ データベース風のマージ`df1.merge(df2)`とかについては、[1]とかを参考にしてください。

その他くわしきことは、公式ページ http://pandas.pydata.org/pandas-docs/version/0.19.1/generated/pandas.DataFrame.merge.html とか、 http://sinhrks.hatenablog.com/entry/2015/01/28/073327 がいいかな、と思います。

後者については、

```markdown
+ 縦方向のシンプルな連結 DataFrame.append
+ 柔軟な連結 pd.concat
+ 列の値による結合 pd.merge
+ index による結合 DataFrame.join (mergeのmake easy版)
```

のようなことが図、例とともに書かれているので、わかりやすいです。


-----

### DataFrameの内容を修正する

#### index, columnsの名前を全部変更したい

```python
# indexの名前変更
df.index = ['one', 'two', 'three']
# indexの番号再振り分け
df.reset_index(drop = True) # インデックスを再振り分け(0番から~)

# columnsの名前変更
## table作成=>編集後はcolumnが想定する並びになっていない可能性があるので、
## 明示的にcolumnの並びを指定した方が無難。
df = df[['old_a', 'old_b', 'old_c']] 
df.columns = ['new_a', 'new_b', 'new_c']

# もしくは、df.renameを使う
df = df[['old_a', 'old_b', 'old_c']] #columnの並び順を気にしなければどっちでも。
# renameは破壊的メソッドではないので、左辺値に代入する必要がある。parameterはcolumnsを指定。(axisパラメータではないことに注意)
df = df.rename(columns = {'old_a' : 'new_a', 'old_b' : 'new_b', 'old_c' : 'new_c'})
```

Note1) 一部のindex, column名を変更したいときにも`df.rename`が使える。(`index`か`columns`パラメータに`dict`型(変更前-変更後の対応表として)を指定する。`axis`パラメータはないので、注意すること。あとは、`column`でなく`columns`(`s`をつける))


Note2) `reindex`は既存のindexの位置の入れ替えであって、index名の変更ではない。
set_indexは`df.set_index(['c1','c0'])`というように、特定の1つ以上の列をインデックスとして使用した新しいオブジェクトを作成する。indexの名前の変更するメソッドではないことに注意。`reset_index`は階層型インデックスが列に変換される。ちょうど`set_index <=> reset_index`の関係。


#### 代入

+ ある列内で所要の条件に当てはまった行の別の列を変更する

```python
# 'A'列に着目して、'wrong'である行を選択し、それらの行の'B'列を'sth'に変更する
df.loc[df['A'] == 'wrong', 'B'] = 'sth'
```

+ `sort_index`(indexを軸にしたソートの場合), `sort_values`(特定の列(`by`で指定)で昇順,降順にsortできる(降順のばあいは, `ascending = False`を指定)



### 抽出

+ `df[･]`に複数のブールインデックスを入れたいばあい。

http://naotoogawa.hatenablog.jp/entry/2015/09/12/PandasのDataFrameの嵌りどころ

```python
# それぞれのブールインデックスはカッコで囲むこと
df = df[(df['A'] > 0) | (df['B'] > 0)]
```


+ `df[･]`の要素それぞれについて複数の候補内にあるかを調べたい場合

```python
# applyはSeriesを引数にとる関数(ラムダ式)を第一引数に突っ込む
# mapは要素を引数にとる関数(ラムダ式)を第一引数に突っ込む
df = df[df['A'].map(lambda d : d in listA)]
```


### 削除

行、列の削除は`df.drop`で(non-destractive)。axisを指定すれば、行でも列でも削除可能

```python
df = df.drop("A", axis=1)
# columnが'A', 'B', .. 'F'で'C'列から'F'列まで削除したいときとかは、以下のようにする方が多い
df = df[['A', 'B']]
```

#### naを取り除く

http://nekoyukimmm.hatenablog.com/entry/2015/02/25/222414 を参照。

+ NANの含まれている部分の行や列を丸ごと削除したい時は`how = 'all'`を指定すればOKです。

## ブールインデックス参照

+ ブール型の取得方法

```python
# DataFrame型を返す
df.apply(lambda ser: ser % 2 == 0)
df.applymap(lambda x: x % 2 == 0)
df['goal'] == 0
df.isin([1,2])
df = df[~df.index.duplicated()] # indexの重複の削除(2回目以降に出てくるデータを削除)
# Series型を返す
df.apply(lambda ser : (ser > 0).any())
df['A'].map(lambda x : x > -1)
serA > serB # series型
-bool_ser # boolインデックスの要素のインデックスをひっくり返す
```



```python
# 第二引数は第一引数でFalseになる要素のみ
df['A'].where(df['A'] > 0, -df['A']) # absのSeries版(第一引数に当てはまらなければ、負の符号をつける(つまり、負に負をかけるので、正になる)
(df['goal'] == 0).all() # 条件に全てあてハマっていれば、True
df.apply(lambda ser: ser % 2 == 0)
(df['cdf(%)'] < 90).sum() # 条件に当てはまる個数をカウント
df.where(df % 3 == 0, -df)
```


### NA値が割り当てられる可能性のある書き方

結構、`NA`周りではまることも多いので、どこで　`NA`値を発生させる可能性があるのかをメモ。

+ コンストラクタのindex, columns

```python
dic = dict(zip(list('ABCD'), [3,4,6,2])) # dictを生成
ser = Series(dic, index = list('ABCDE'))
# dicにないE列がNANになっている
#A    3.0
#B    4.0
#C    6.0
#D    2.0
#E    NaN
#dtype: float64
```

+ DataFrameコンストラクタのdataパラメタに辞書に乗っていない

(例)

```python
pop = {'Nevada' : {2001 : 2.4, 2002 : 2.9},
       'Ohio' : {2000 : 1.5, 2001 : 1.7}}
df = DataFrame(pop)

      Nevada  Ohio
2000     NaN   1.5
2001     2.4   1.7
2002     2.9   NaN
```

+ `df.reindex`でindexパラメータにdfのindexに含まれないものが入っている場合。

例は省略

+ `loc`フィールドを用いたインデックス参照

```python
df.loc[[2002, 2001, 1999], ['Alaska', 'Nevada']]

      Alaska  Nevada
2002     NaN     2.9
2001     NaN     2.4
1999     NaN     NaN
```

Note) `df['non_exists']`, `df.loc[:, 'non_exists']`　(columnに無い名前を指定してしまう)とエラー。

+ データフレーム同士の加算(`index` or `column`の対応しない要素がNANになる)

+ `merge, join`に`how='outer'`とかを指定した場合

+ `append, concat`(indexが対応していない場合)


### NA値を取り除く方法

+ `df.dropna`(parameterはhowとかaxisとか), `df.fillna(0)`(NA値を一律0にする

+ `df.reindex`に`fill_value`パラメータまたは `method`パラメータを指定

+ `combine_first` : `old_df` のNA値のみが第一引数によって補完される。(old_dfで要素が0になっている箇所はパッチあての対象とならないので、注意！)

```python
# df .. indexが飛び飛び(index.name : B_idx, columns = ['A'] => indexを連番(0~89)として、補間値を0としたい
old_df = DataFrame(index = range(90), columns = 'A')
new_df = old_df.combine_first(df).fillna(0) # index.nameは消える
```


### 文字列の操作

特に`Series`の文字列操作は地味によく使うので。要素だけでなく、index名、column名に対しても、使えます！


詳しくは、 http://pandas.pydata.org/pandas-docs/stable/text.html (特に最下部)をみてください。DataFrame内で文字列操作をしたい時は、だいたいここみれば解決します。

使い所としては、例えば、ある正規表現にマッチする行だけを抜き出したい場合とか。

```python
# dfの'A'列の中で先頭が英小文字に当たる行のみを抽出して、dfを更新する
r = '^[a-z]'
df = df[df['A'].str.match(r)] # df['A'].str.match(r)はブールインデックス
```

-----

# 参考文献


[1]  [Pythonによるデータ分析入門-―NumPy、pandasを使ったデータ処理](https://www.amazon.co.jp/Pythonによるデータ分析入門-―NumPy、pandasを使ったデータ処理-Wes-McKinney/dp/4873116554)

[2]  http://sinhrks.hatenablog.com/entry/2015/01/28/073327

[3]  Documentation http://pandas.pydata.org/pandas-docs/stable/api.html