【初学者向け】データフレームの取扱説明書

# 目的
データフレームからの列・行の取り出し方について一通りまとめる。

# 背景
データを読み込んでデータフレームを作って、特定の行や列を参照したり、書き換えしたりしている。困ったときには、都度ググって対処しているが、基本的な使い方を一通り覚えたく、まとめてみた。
私がよく困ったのは、データフレームからデータを取り出したいのに、Series とか、１行DataFrame として取り出してしまい、参照や書き換えがうまくできないことがよくありました。そもそも、このとり方をすると、どんな型として取得できるのかを理解してなく、混乱し、苦労していました。
そのため、今回、自分のために一通りまとめてみましたが、初学者の方の参考にもなれば幸いです。

# サンプルの dataframe を作成

* ３行x３列のサンプルデータフレームを返す関数を作成する。
* データとそのタイプを表示する関数を作成する。タイトルは[]カギカッコ付きで表示する。

```python
import pandas as pd

# サンプルの dataframe を取得
def get_sample_df():
  return pd.DataFrame( {'column0':[0,1,2],'column1':[10,11,12],'column2':[20,21,22],}, index=['index0','index1','index2'])

# データとタイプを表示
def print_data_type(title,data):
  print(f'[{title}]')
  print(data)
  print(type(data))  

print_data_type('SampleDataFrame',get_sample_df())
```
```
[SampleDataFrame]
        column0  column1  column2
index0        0       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
```

以下、このサンプルデータフレームを使って、列・行を取り出し、どのようなデータがどのようなデータ型で取得できるかを確認する。
書き換えできるかを確認する際は、わかりやすいように、100（など三桁）に書き換える。

# 列指定
* カラム名を１個指定すると、 Series が取得できる。
* カラム名のリストを指定すると、複数列のDataFrameが取得できる。
* カラム名のスライスを指定すると、エラーにはならないが、複数列のDataFrame（空）が取得できる。（意味なし）
```python
# カラム名を１個指定
print_data_type('カラム名を１個指定',get_sample_df()['column0'])
```
```
[カラム名を１個指定]
index0    0
index1    1
index2    2
Name: column0, dtype: int64
<class 'pandas.core.series.Series'>
```

```python
# カラム名のリストを指定
print_data_type('カラム名のリストを指定',get_sample_df()[['column0','column2']])
```
```
[カラム名のリストを指定]
        column0  column2
index0        0       20
index1        1       21
index2        2       22
<class 'pandas.core.frame.DataFrame'>
```

```python
# カラム名のスライスを指定
print_data_type('カラム名のスライスを指定',get_sample_df()['column0':'column2'])
```
```
[カラム名のスライスを指定]
Empty DataFrame
Columns: [column0, column1, column2]
Index: []
<class 'pandas.core.frame.DataFrame'>
```

# 行指定
* インデックス名を１個、もしくはリスト指定はできない。
* インデックス名のスライスを指定すると、複数行（連続）のDataFrameが取得できる。
* インデックス名のスライス（同じ名称）を指定すると、１行の DataFrame が取得できる。（DataFrame から１行指定して取得した DataFrame 参照）
```python
# インデックス名のスライスを指定
print_data_type('インデックス名のスライスを指定',get_sample_df()['index0':'index2'])
```
```
[インデックス名のスライスを指定]
        column0  column1  column2
index0        0       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
```


```python
# インデックス名のスライス（同じ名称）を指定
print_data_type('インデックス名のスライス（同じ名称）を指定',get_sample_df()['index0':'index0'])
```
```
[インデックス名のスライス（同じ名称）を指定]
        column0  column1  column2
index0        0       10       20
<class 'pandas.core.frame.DataFrame'>
```

# loc
* インデックス名、カラム名を１個指定すると、１要素が取得でき、書き換えできる。
* インデックス名、カラム名とも、リストか、スライスを指定でき、複数行x複数列が取得できる。１行、もしくは、１列の場合は、Series、複数行x複数列の場合は、Dataframe。
* 全列を取得する場合には、カラム名の省略が可能。
* インデックス名とカラム名は、リストとスライスの混在が可能。（インデックス名はリスト、カラム名はスライス。もしくは、その逆）
```python
# インデックス名、カラム名を１個指定
print_data_type('インデックス名、カラム名を１個指定',get_sample_df().loc['index0','column0'])
# 書き換できる
df = get_sample_df()
print_data_type('書き換え前',df)
df.loc['index0','column0'] = 100
print_data_type('書き換え後',df)
```
```
[インデックス名、カラム名を１個指定]
0
<class 'numpy.int64'>
[書き換え前]
        column0  column1  column2
index0        0       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
[書き換え後]
        column0  column1  column2
index0      100       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
```

```python
# カラム名を１個指定
print_data_type('カラム名を１個指定',get_sample_df().loc[:,'column0'])
```
```
[カラム名を１個指定]
index0    0
index1    1
index2    2
Name: column0, dtype: int64
<class 'pandas.core.series.Series'>
```

```python
# カラム名をリスト指定
print_data_type('カラム名をリスト指定',get_sample_df().loc[:,['column0','column2']])
```
```
[カラム名をリスト指定]
        column0  column2
index0        0       20
index1        1       21
index2        2       22
<class 'pandas.core.frame.DataFrame'>
```

```python
# カラム名をスライス指定
print_data_type('カラム名をスライス指定',get_sample_df().loc[:,'column0':'column2'])
```
```
[カラム名をスライス指定]
        column0  column1  column2
index0        0       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
```

```python
# インデックス名を１個指定
print_data_type('インデックス名を１個指定',get_sample_df().loc['index0',:])
# インデックス名を１個指定（列は省略可能）
print_data_type('インデックス名を１個指定（列は省略可能）',get_sample_df().loc['index0'])
```
```
[インデックス名を１個指定]
column0     0
column1    10
column2    20
Name: index0, dtype: int64
<class 'pandas.core.series.Series'>
[インデックス名を１個指定（列は省略可能）]
column0     0
column1    10
column2    20
Name: index0, dtype: int64
<class 'pandas.core.series.Series'>
```

```python
# インデックス名をリスト指定
print_data_type('インデックス名をリスト指定',get_sample_df().loc[['index0','index2']])
```
```
[インデックス名をリスト指定]
        column0  column1  column2
index0        0       10       20
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
```

```python
# インデックス名をスライス指定
print_data_type('インデックス名をスライス指定',get_sample_df().loc['index0':'index2'])
```
```
[インデックス名をスライス指定]
        column0  column1  column2
index0        0       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
```

```python
# インデックス名をスライス指定、カラム名をリスト指定
print_data_type('インデックス名をスライス指定、カラム名をリスト指定',get_sample_df().loc['index0':'index2',['column0','column2']])
```
```
[インデックス名をスライス指定、カラム名をリスト指定]
        column0  column2
index0        0       20
index1        1       21
index2        2       22
<class 'pandas.core.frame.DataFrame'>
```

# iloc
* インデックス名、カラム名が、行番号、列番号になっただけで、locと同じ。
```python
# 行番号、列番号を１個指定
print_data_type('行番号、列番号を１個指定',get_sample_df().iloc[0,0])
# 書き換できる
df = get_sample_df()
print_data_type('書き換え前',df)
df.iloc[0,0] = 100
print_data_type('書き換え後',df)
```
```
[行番号、列番号を１個指定]
0
<class 'numpy.int64'>
[書き換え前]
        column0  column1  column2
index0        0       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
[書き換え後]
        column0  column1  column2
index0      100       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
```

```python
# 列番号を１個指定
print_data_type('列番号を１個指定',get_sample_df().iloc[:,0])
```
```
[列番号を１個指定]
index0    0
index1    1
index2    2
Name: column0, dtype: int64
<class 'pandas.core.series.Series'>
```

```python
# 列番号をリスト指定
print_data_type('列番号をリスト指定',get_sample_df().iloc[:,[0,2]])
```
```
[列番号をリスト指定]
        column0  column2
index0        0       20
index1        1       21
index2        2       22
<class 'pandas.core.frame.DataFrame'>
```

```python
# 列番号をスライス指定
print_data_type('列番号をスライス指定',get_sample_df().iloc[:,0:3])
```
```
[列番号をスライス指定]
        column0  column1  column2
index0        0       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
```

```python
# 行番号を１個指定
print_data_type('行番号を１個指定',get_sample_df().iloc[0,:])
# 行番号を１個指定（列は省略可能）
print_data_type('行番号を１個指定（列は省略可能）',get_sample_df().iloc[0])
```
```
[行番号を１個指定]
column0     0
column1    10
column2    20
Name: index0, dtype: int64
<class 'pandas.core.series.Series'>
[行番号を１個指定（列は省略可能）]
column0     0
column1    10
column2    20
Name: index0, dtype: int64
<class 'pandas.core.series.Series'>
```

```python
# 行番号をリスト指定
print_data_type('行番号をリスト指定',get_sample_df().iloc[[0,2]])
```
```
[行番号をリスト指定]
        column0  column1  column2
index0        0       10       20
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
```

```python
# 行番号をスライス指定
print_data_type('行番号をスライス指定',get_sample_df().iloc[0:3])
```
```
[行番号をスライス指定]
        column0  column1  column2
index0        0       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
```

```python
# 行番号をスライス指定、列番号をリスト指定
print_data_type('行番号をスライス指定、列番号をリスト指定',get_sample_df().iloc[0:3,[0,2]])
```
```
[行番号をスライス指定、列番号をリスト指定]
        column0  column2
index0        0       20
index1        1       21
index2        2       22
<class 'pandas.core.frame.DataFrame'>
```

# at
* インデックス名、カラム名を指定して、１要素（１行１列）の取得のみに対応している。
* 機能的には、locの１行１列指定と同じだが、速度が速い。
```python
# インデックス名、カラム名を１個指定
print_data_type('インデックス名、カラム名を１個指定',get_sample_df().at['index0','column0'])
# 書き換できる
df = get_sample_df()
print_data_type('書き換え前',df)
df.at['index0','column0'] = 100
print_data_type('書き換え後',df)
```
```
[インデックス名、カラム名を１個指定]
0
<class 'numpy.int64'>
[書き換え前]
        column0  column1  column2
index0        0       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
[書き換え後]
        column0  column1  column2
index0      100       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
```

# iat
* インデックス名、カラム名が、行番号、列番号になっただけで、atと同じ。
```python
# 行番号、列番号を１個指定
print_data_type('行番号、列番号を１個指定',get_sample_df().iat[0,0])
# 書き換できる
df = get_sample_df()
print_data_type('書き換え前',df)
df.iat[0,0] = 100
print_data_type('書き換え後',df)
```
```
[行番号、列番号を１個指定]
0
<class 'numpy.int64'>
[書き換え前]
        column0  column1  column2
index0        0       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
[書き換え後]
        column0  column1  column2
index0      100       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
```

# DataFrame から１列指定して取得した Series
* インデックス名称指定、行番号指定、どちらでも要素を取得できる。（１個、リスト、スライス）
* インデックス名称１個の場合は、.の後にインデックス名称を記述しても取得できる。
* DataFrame から１列指定して取得した Series を書き換えると、元の dataframe も書き換わる。
* 元の DataFrame を書き換えたくない場合は、.copy() で１列取得する。

### インデックス名称指定
```python
# DataFrame から１列指定して取得した Series
srs_column0 = get_sample_df()['column0']
print_data_type('DataFrame から１列指定して取得した Series',srs_column0)

# １要素取得
print_data_type('１要素取得',srs_column0['index0'])
# １要素取得（.の後にインデックス名称）
print_data_type('１要素取得（.の後にインデックス名称）',srs_column0.index0)

# リスト指定
print_data_type('リスト指定',srs_column0[['index0','index2']])

# スライス指定
print_data_type('スライス指定',srs_column0['index0':'index2'])
```
```
[DataFrame から１列指定して取得した Series]
index0    0
index1    1
index2    2
Name: column0, dtype: int64
<class 'pandas.core.series.Series'>
[１要素取得]
0
<class 'numpy.int64'>
[１要素取得（.の後にインデックス名称）]
0
<class 'numpy.int64'>
[リスト指定]
index0    0
index2    2
Name: column0, dtype: int64
<class 'pandas.core.series.Series'>
[スライス指定]
index0    0
index1    1
index2    2
Name: column0, dtype: int64
<class 'pandas.core.series.Series'>
```

###  行番号
```python
# DataFrame から１列指定して取得した Series
srs_column0 = get_sample_df()['column0']
print_data_type('DataFrame から１列指定して取得した Series',srs_column0)

# １行取得
print_data_type('１行取得',srs_column0[0])

# リスト指定
print_data_type('リスト指定',srs_column0[[0,2]])

# スライス指定
print_data_type('スライス指定',srs_column0[0:3])```
```
```
[DataFrame から１列指定して取得した Series]
index0    0
index1    1
index2    2
Name: column0, dtype: int64
<class 'pandas.core.series.Series'>
[１行取得]
0
<class 'numpy.int64'>
[リスト指定]
index0    0
index2    2
Name: column0, dtype: int64
<class 'pandas.core.series.Series'>
[スライス指定]
index0    0
index1    1
index2    2
Name: column0, dtype: int64
<class 'pandas.core.series.Series'>
```

### DataFrame から１列指定して取得した Series を書き換えると、元の dataframe も書き換わる。
```python
df = get_sample_df()
print_data_type('書き換え前',df)
srs_column0 = df['column0']
# 書き換できる
srs_column0['index0'] = 100
srs_column0[1] = 200
srs_column0.index2 = 300
# 元のdataframeも書き換わる
print_data_type('書き換え後',df)
```
```
[書き換え前]
        column0  column1  column2
index0        0       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
[書き換え後]
        column0  column1  column2
index0      100       10       20
index1      200       11       21
index2      300       12       22
<class 'pandas.core.frame.DataFrame'>
```

### 元の DataFrame を書き換えたくない場合は、.copy() で１列取得する。
```python
# 列指定のコピーで series を取得すると、元の dataframe は書き換わらない
df = get_sample_df()
print_data_type('書き換え前',df)
srs_column0 = df['column0'].copy()
srs_column0['index0'] = 100
srs_column0[1] = 200
print_data_type('書き換え後',srs_column0)
print_data_type('書き換え後',df)
```
```
[書き換え前]
        column0  column1  column2
index0        0       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
[書き換え後]
index0    100
index1    200
index2      2
Name: column0, dtype: int64
<class 'pandas.core.series.Series'>
[書き換え後]
        column0  column1  column2
index0        0       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
```

# DataFrame から１行指定して取得した DataFrame
* １行取り出すことはできても、スライス指定なので、DataFrameとして取得される。
* なので、次に１列指定して取り出しても、 Series となるので、そのままでは書き換えができない。
  * 元の DataFrame は書き換わらない。
  *  ワーニングも出るので非推奨。
* １行の DataFrame を転置しても、 Series にはならない。
* 書き換える場合は、loc/iloc で１行取り出し、Series として扱う。（推奨）
```python
# DataFrame から１行指定して取得した DataFrame
df = get_sample_df()
df_index0 = df['index0':'index0']
print_data_type('DataFrame から１行指定して取得した DataFrame',df_index0)

# １列取得
print_data_type('１列取得',df_index0['column0'])
```
```
[DataFrame から１行指定して取得した DataFrame]
        column0  column1  column2
index0        0       10       20
<class 'pandas.core.frame.DataFrame'>
[１列取得]
index0    0
Name: column0, dtype: int64
<class 'pandas.core.series.Series'>
```

```python
# 元の dataframe は書き換わらない
df_index0['column0'] = 100
print_data_type('書き換え後',df_index0)
print_data_type('書き換え後',df)
```
```
[書き換え後]
        column0  column1  column2
index0      100       10       20
<class 'pandas.core.frame.DataFrame'>
[書き換え後]
        column0  column1  column2
index0        0       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
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: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  df_index0['column0'] = 100
```

```python
# １行 dataframe を転置
df_index0_T = df_index0.T
print_data_type('１行 dataframe を転置',df_index0_T)
```
```
[１行 dataframe を転置]
         index0
column0     100
column1      10
column2      20
<class 'pandas.core.frame.DataFrame'>
```

### 書き換える場合は、loc/iloc で１行取り出し、Series として扱う。
```python
# 書き換える場合は、loc/iloc で１行取り出し、Series として扱う。

# DataFrame からlocで行を取り出したSeries
df = get_sample_df()
df_index0 = df.loc['index0']
print_data_type('書き換え前',df)
print_data_type('書き換え前',df_index0)
print_data_type('DataFrame からlocで行を取り出したSeries',df_index0)

df_index0['column0'] = 100
print_data_type('書き換え後',df_index0)
print_data_type('書き換え後',df)
```
```
[書き換え前]
        column0  column1  column2
index0        0       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
[書き換え前]
column0     0
column1    10
column2    20
Name: index0, dtype: int64
<class 'pandas.core.series.Series'>
[DataFrame からlocで行を取り出したSeries]
column0     0
column1    10
column2    20
Name: index0, dtype: int64
<class 'pandas.core.series.Series'>
[書き換え後]
column0    100
column1     10
column2     20
Name: index0, dtype: int64
<class 'pandas.core.series.Series'>
[書き換え後]
        column0  column1  column2
index0      100       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
```

# ループ

* DataFrame をそのままループしても、カラム名しか取得できない。
* 列方向にループするには、items()
  *  Series として取得できるので、行名、行番号指定で書き換え可能
* 行方向にループするには、iterrows()
  *  Series として取得できるので、列名、列番号指定で書き換え可能

### DataFrame をそのままループ
長くなるので、１回でbreakする
```python
df = get_sample_df()
for column in df:
  print_data_type('column',column)
  break
```
```
[column]
column0
<class 'str'>
```

### 列方向にループ
まず、列方向、行方向にどのようなデータが格納されているかすぐに確認できるように、データフレームの中身を再表示しておく。
```python
print_data_type('SampleDataFrame',get_sample_df())
```
```
[SampleDataFrame]
        column0  column1  column2
index0        0       10       20
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
```
```python
df = get_sample_df()
# items で、列方向にループ
for column, data in df.items():
  print_data_type('column',column)
  print_data_type('data',data)
  data['index0'] = 100
  data[1] = 200
  data.index2 = 300
  break
print_data_type('書き換え後',df)
```
```
[column]
column0
<class 'str'>
[data]
index0    0
index1    1
index2    2
Name: column0, dtype: int64
<class 'pandas.core.series.Series'>
[書き換え後]
        column0  column1  column2
index0      100       10       20
index1      200       11       21
index2      300       12       22
<class 'pandas.core.frame.DataFrame'>
```

### 行方向にループ
```python
df = get_sample_df()
# iterrows で、行をループ
for index, data in df.iterrows():
  print_data_type('index',index)
  print_data_type('data',data)
  data['column0'] = 100
  data[1] = 200
  data.column2 = 300
  break
print_data_type('書き換え後',df)
```
```
[index]
index0
<class 'str'>
[data]
column0     0
column1    10
column2    20
Name: index0, dtype: int64
<class 'pandas.core.series.Series'>
[書き換え後]
        column0  column1  column2
index0      100      200      300
index1        1       11       21
index2        2       12       22
<class 'pandas.core.frame.DataFrame'>
```

# まとめ
データフレームからの列・行の取り出し方について一通りまとめみたが、難しいことはまったくなかった。基本的なことなので、都度ググることなく、スラスラと書いていけるようにしていきたい。