DataFrameレシピ: 行列指定して出力

PandasのDataFrameをいつもググりながら使っていましたが、同じことをやろうとしてもいくつか方法があり、整理をしてみました。Jupyter前提で、行と列を指定して内容を見る方法の整理です。
[「pandasで任意の位置の値を取得・変更するat, iat, loc, iloc」](https://note.nkmk.me/python-pandas-at-iat-loc-iloc/)を参考にしました。
似た内容として[記事「DataFrameレシピ: データ抽出条件」](https://qiita.com/FukuharaYohei/items/c3cc966b11f5fc3ed0bf)にブールインデックスや`query`関数を使った抽出方法も書いています。

# 環境
2021年2月にGoogle Colaboratory使っています。そのため、Pythonやそのパッケージはそのままのバージョンで使っています。

|種類|バージョン|
|:--|:--|
|Python|3.6.9|

上記環境で、以下のPython追加パッケージバージョンでした。

|種類|バージョン|
|:--|:--|
|jupyter|1.0.0|
|numexpr|2.7.2|
|numpy|1.19.5|
|pandas|1.1.5|

# 基本: 4つのプロパティ(iat, at, iloc, loc)
DataFrameには任意の位置を取得するためのプロパティがあります(関数ではありません)。そのため`df.loc()`ではなく`df.loc[]`のように使います。

||数字アクセス|名前アクセス|
|:-:|:-:|:-:|
|単独値アクセス|[iat](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.iat.html)|[at](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.at.html)|
|複数値アクセス|[iloc](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.iloc.html)|[loc](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.loc.html)|

# まとめ
まとめからです。データ作成部分と行列指定部分だけピックアップしました。

## データ作成

```python
> df = pd.DataFrame({'A': [-20, -10, 0, 10, 20],
                   'B': [1, 2, 3, 4, 5],
                   'C': ['a', 'b', 'b', 'b', 'a'],
                   'D': ['abc', 'bbb', 'bcb', 'bbb', 'abb']})
> print(df)
    A  B  C    D
0 -20  1  a  abc
1 -10  2  b  bbb
2   0  3  b  bcb
3  10  4  b  bbb
4  20  5  a  abb
```

## 行列指定

```python
# 行列指定
print(df.at[1, 'B'])
print(df.loc[1, 'B'])
print(df.iat[1, 1])
print(df.iloc[1, 1])
print(df.B.at[1])

# 単一列のみ指定
print(df['B'])
print(df.loc[:,['B']])  #df.loc[:,'B'] でもOKだが出力がストライプなし
print(df.iloc[:, [1]])  #df.iloc[:,1] でもOKだが出力がストライプなし

# 複数列指定
print(df.loc[:,['B', 'C']])
print(df.iloc[:, [1, 2]])
print(df.loc[:,'B':'D']) # スライス方式
print(df.iloc[:, 1:4])   # スライス方式

# 単一行指定
print(df.loc[1, :])
print(df.iloc[1, : ]) # indexが数値なのでlocと変わらず

# 複数行指定
print(df.loc[[1, 2], :])
print(df.iloc[[1, 2], : ]) # indexが数値なのでlocと変わらず
print(df.loc[1:3, :])   # スライス方式
print(df.iloc[1:3, : ]) # スライス方式: indexが数値なのでlocと変わらず
```

# 詳細説明
## DataFrame作成
まずはDataFrameを作成。

```python
df = pd.DataFrame({'A': [-20, -10, 0, 10, 20],
                   'B': [1, 2, 3, 4, 5],
                   'C': ['a', 'b', 'b', 'b', 'a']})
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/3371dc10-b35d-b856-5bc6-c733a1978288.png)

## loc / ilocプロパティ
[`loc`プロパティ](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.loc.html)または[`iloc`プロパティ](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.iloc.html)を使って列と行を指定します。最初の引数がインデックス名で最後のインデックスが列名です。

```python
> df.loc[1, 'A']
-10
> df.iloc[1, 0]
-10
```

両引数とも配列で複数指定ができます。引数を配列形式にするとストライプのかかった表形式で出力してくれ、少し見やすいです(`[]`で囲むのが少し面倒ですが)。

```python
df.loc[[1, 3],['A','C']]
# df.iloc[[1, 3],[0, 2]] でも同じ
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/3594cb27-b516-9666-b704-397981aaf939.png)

### 行指定
行だけを指定すると各列を返します。

```python
> df.loc[1]
# df.iloc[1]でも同じ
A    -10
B      2
C      b
Name: 1, dtype: object
```

複数インデックスの場合はこんな感じ。

```python
df.loc[[1, 3]]
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/13a5ec6f-ea5f-8e4e-61a1-321e6f6c469b.png)

スライスを使った範囲指定も可能

```python
df.loc[1:3]
# df.iloc[[1, 3]] でも同じ
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/037af749-8f88-f2db-8a69-10a51ed4c193.png)

### 列指定
1列だけ出します。

```python
df.loc[:,'A']
> df.A や df.iloc[:, 0]でも同じ
0   -20
1   -10
2     0
3    10
4    20
Name: A, dtype: int64
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/a64f5c0b-5403-ebf7-d1f8-519eb7266f24.png)

配列にして列を渡せばストライプの表形式で出力できます。少し見やすいですね。

```python
df.loc[:,['A']]
# df.iloc[: , [0]] でも同じ
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/8907fe6f-9aef-1a79-1a13-e5c974cd63ed.png)


複数列を配列で渡します。

```python
df.loc[:,['A', 'C']]
# df.iloc[:,[0, 2]] でも同じ
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/61940c96-1000-6502-a5e4-b953b3e989db.png)

### スライス
`start:stop:step`のスライスを使って指定ができます。`step`は省略可能で、あまり使わないと思います。注意点は、ilocの場合は`stop`の数の1つ前までですが、`loc`の場合は`stop`の行/列までアクセスします。

```python
df.loc[:, 'B':'C']
# df.iloc[:, 1:3] も同じ
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/8c387194-9f3c-df03-ae40-4f9a8426adfc.png)

行のスライス使用。

```python
df.loc[1:3, :]
# df.iloc[1:3, :] も同じ
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/2574d068-eeca-d871-c211-cbbd71193113.png)

行・列のスライス使用。

```python
df.loc[1:3, 'B':'C']
# df.iloc[1:3, 1:3] も同じ
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/16557244-8e19-49dd-dcac-0fc8375c3d29.png)


## at / iat プロパティ
本当はこちらの方がシンプルなのでloc / ilocより先に書くべきかもしれませんが、あまり使いたいと思ったことがないので後ろにしています。loc / iloc と違って単独値にアクセスするためのプロパティです。

```python
> df.at[1, 'B']
# df.iat[1, 1] でもOK
2
```

DataFrameだけだなくSeriesオブジェクトにも使えます。

```python
df.B.at[1]
```

## 実践 Tips
### 基本: Boolean で対象判定
[`loc`プロパティ](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.loc.html)で`True`と`False`を使うこともでき、これによる応用が役に立ちます。`True`だとアクセス対象です。**ブールインデックス**と呼ぶようです。
イマイチ違いがわかりませんが、[`loc`プロパティ](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.loc.html)なしでも可能です。

```python
df[[True, False, True, False, True]]
# df.loc[[True, False, True, False, True]] も同じ
# df.iloc[[True, False, True, False, True]] も同じ
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/b7a14772-bef7-0720-9818-1fe77f1fd61d.png)

もちろん、列指定してもよし。ただし、列指定もすると、`loc`プロパティが必要

```python
df.loc[[True, False, True, False, True],['B']]
# この場合はdf[[True, False, True, False, True],['B']] はNG
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/2cc28bc3-402a-087c-bd52-a1420bff232b.png)

以下の書き方でもいいが、ストライプ形式では出力されない。この場合は列指定をプロパティ内でしていないので`loc`不要。

```python
> df[[True, False, True, False, True]].B 
0    1
2    3
4    5
Name: B, dtype: int64
```

### 抽出との組み合わせ
ここからが実践的な内容です。
例えば、列で値がXXと一致する場合の各行の値を見たければこう書きます。

```python
df[df['C']=='b']
# df['C']=='b' の部分が True/ Falseを返す
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/64cc94bd-e68a-c419-cac6-763fa6145d70.png)

### 抽出して値設定
値が一致したらば新しい列'New'を作って値を設定することもできます。`query`関数はTrue, Falseを返さないので、この場合は使えない気がします(あまり調べていないです)。

```python
df.loc[df['C']=='b', 'New'] = 'bbbb'
df
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/8aa08152-240a-859b-95d1-f83e28eb425a.png)

上では`New`列がなかったので新列を作るような動きになっています。列名が存在する場合には、その列名にアクセスします。上の状態の後に実行すると、列`New`が既に存在するので新規列は作成されません。

```python
df.loc[df['B']==3, 'New'] = 'ahhh'
df
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/dfcfdb27-0770-7114-a0dc-ceab3dbfce34.png)