苦しみながら覚えるPandas入門[Part1]

## はじめに

Pythonでデータ分析をする際に避けては通れないライブラリ、それがPandasです。
ただこのPandas、初心者にはめちゃくちゃハードルが高い、、、
私もめちゃ苦労したので、基礎からしっかりとまとめようと思います。
私自身がPandasの上級者というわけではありませんので、コメントやアドバイス頂ければ幸いです。
また、今回の記事は3部構成の予定です。
(予定なので、変更になる可能性は大いにあります、、、)

* 苦しみながら覚えるPandas入門[Part1] ← イマココ
    * SeriesとDataFrameの作成方法
    * DataFrameの各種属性について解説
* 苦しみながら覚えるPandas入門[Part2]
    * DataFrameの各種メソッドについて解説
* 苦しみながら覚えるPandas入門[Part3]
    * DataFrameの各種メソッドについて解説(続き)
    * 値の更新、検索について
    * DataFrameの計算について


では始めましょう(*^▽^*)

### 注意事項

下記ライブラリのインポートは今後の表記では省略しますのでご了承ください。

```python
import pandas as pd
import numpy as np
```

## Seriesについて

Pandasには大きく分けてSeriesとDataFrameクラスの二つが存在します。
違いについて簡単に説明すると、Seriesは1次元のデータ(ベクトル)を扱うことができ、DataFrameは2次元のデータ(行列)を扱うことができます。
では、まずSeriesについて確認していきましょう。
[pandas.Series](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.html#pandas.Series)

### Seriesの作成(辞書指定)

```python
sr = pd.Series({'taro': 'kyoto', 'jiro': 'osaka', 'saburo': 'nara'})
print(sr.head())
```

![img123.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192873/58ebfbd5-2eea-c778-1bae-3600aaeeef1c.png)

上記コードでは、辞書の属性名がSeriesの行ラベル(index)に、辞書の値がSeriesの値に対応しています。

### Seriesの作成(配列指定)

```python
sr = pd.Series(['kyoto', 'osaka', 'nara'], index=['taro', 'jiro','saburo'] )
print(sr.head())
```

![img123.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192873/58ebfbd5-2eea-c778-1bae-3600aaeeef1c.png)

上記のように行ラベル(index)を設定することができます。
ちなみに、行ラベルを設定しなくてもエラーにはなりませんが、明示的に設定していない場合は自動的に0,1という数字が設定されます。

Seriesにはまだまだ多くの機能がありますが、DataFrameと重複する内容が多くなるため省略します。
**ただし、DataFrameとSeriesでは属性名が同じであっても使い方が異なるので注意してください。**
(これもPandasを難しくしている要因の一つかなと、、、)

## DataFrameについて

PandasといえばDataFrameです。
DataFrameについて公式のリファレンスを見てみましょう。
[pandas.DataFrame](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html#pandas.DataFrame)

pandas.DataFrameのコンストラクタの引数として重要なのは下記3つです。

* data
* index
* columns

では、実際のプログラムで見てみましょう。

### DataFrame作成(列)

まずは列データを使ってDatarfameを作る場合です。
辞書の属性名が列ラベル(columns)として利用され、値(配列で登録)が行(index)として扱われます。
(※値を配列にしていないとエラーになります)
↑どうも、適切にindexを設定すれば、配列でなくてもエラーにならないようです。(ややこしいですね、、、)

```python
data = {
  'name': ['taro', 'jiro', 'saburo'],
  'address': ['kyoto', 'osaka', 'nara'],
  'birth': ['2020-01-01T12:00:00', '2020-02-11T12:00:00', '2020-03-22T12:00:00']
}
df = pd.DataFrame(data = data)
print(df.head())
```

![img123.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192873/509acad1-0322-2f95-7205-e3101c7388ea.png)


### DataFrame作成(行)

次は行のデータを使ってDataframeを作成します。
columsがなくてもエラーにはなりませんが、その場合は列のラベルが0,1といった数字が自動的に付与されます。


```python
data = [
  ['taro', 'kyoto', '2020-01-01T12:00:00'],
  ['jiro', 'osaka', '2020-02-11T12:00:00'],
  ['saburo', 'nara', '2020-03-22T12:00:00']
]
df = pd.DataFrame(data = data, columns = ['name', 'address', 'birth'])
print(df.head())
```

![img123.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192873/509acad1-0322-2f95-7205-e3101c7388ea.png)

ちなみに、行ラベル(index)と列ラベル(columns)の両方を設定する場合は、以下のようにします。
indexとcolumnsの両方を設定します。

```python
data = [
  ['taro', 'kyoto', '2020-01-01T12:00:00'],
  ['jiro', 'osaka', '2020-02-11T12:00:00'],
  ['saburo', 'nara', '2020-03-22T12:00:00']
]
df = pd.DataFrame(data = data, index = ['a', 'b', 'c'], columns = ['name', 'address', 'birth'])
print(df.head())
```

![img123.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192873/e0c06a17-f839-7bc1-f783-cc60aceddeda.png)


### DataFrameから列の抽出

データから特定の列データを抜き出したい、という機会は多くあるかと思います。
その場合、後述するlocを使う方が柔軟に絞り込みができるのですが、Dataframeに直接指定しても列を絞り込むことが可能です。
**後述するlocの方が高機能なので、最初はこの方法は覚えておく必要はありません(混乱のもとです)**

```python
data = [
  ['taro', 'kyoto', '2020-01-01T12:00:00'],
  ['jiro', 'osaka', '2020-02-11T12:00:00'],
  ['saburo', 'nara', '2020-03-22T12:00:00']
]
df = pd.DataFrame(data = data, columns = ['name', 'address', 'birth'])
print(df['name'])
```
![img123.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192873/9bb5d41c-189c-5282-2123-0bf2affe98ef.png)

上記コードでは「name」列を抽出しています。
これまでのテーブルの様な表示から変わったことに気づかれることでしょう。
これは、列を抽出した結果が、DataframeではなくSeriesであるためです。

また、複数列を抜き出す場合は、下記のようにします。

```python
df[['name', 'address']]
```

![img123.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192873/8103f7cf-e62c-2027-e73b-395a5231f330.png)


複数列を抜き出した場合、抜き出した結果はSeriesではなくDataframeになります。

### Dataframeから行の抽出

列と同様に行も抽出することが可能です。
抽出するにはスライスを使います。
**後述するlocの方が高機能なので、最初はこの方法は覚えておく必要はありません(混乱のもとです)**

```python
data = [
  ['taro', 'kyoto', '2020-01-01T12:00:00'],
  ['jiro', 'osaka', '2020-02-11T12:00:00'],
  ['saburo', 'nara', '2020-03-22T12:00:00']
]
df = pd.DataFrame(data = data, index = ['a', 'b', 'c'], columns = ['name', 'address', 'birth'])
print(df['a':'b'])
```

![img123.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192873/63e70cc4-21ac-2ec7-3ddf-d1b4f1d5db23.png)


上記では、「a」行と「b」行を抽出しています。
**列と異なり、複数行を個別に選択して抽出することはできません。**
(例えば、a行とc行を抜き出す、といったことができません)
また、a行のみ選択する場合もスライスで指定する必要があります。
(例えば、`print(df['a':'a'])`とするとa行のみ抽出することが可能です)

※ここで疑問を持たれた方がいらっしゃるかもしれません。
通常のスライスでは右側に指定した添字は含まないので、`df['a':'a']`とすると、a行を抽出できないのでは？、ということです。
DataFrameにおいて、行ラベル・列ラベルのスライス指定は右側の添字を含みます(閉区間)が、行番号・列番号のスライスは通常のスライスと同じく、右側の添字を含みません(左閉右開区間)。
(ここも個人的にはハマりどころです、、、)

## Dataframeの属性について

Dataframeには下記のような属性があります。

* T
* at
* iat
* loc
* iloc
* columns
* index
* shape
* values

どれも重要ですので、一つずつ見ていきましょう。

### Dataframe.T 【転置行列の取得】

Tは転置行列を取得することが可能です。
簡単に言うと、行と列を入れ替えたデータを取得できます。

```python
data = [
  ['taro', 'kyoto', '2020-01-01T12:00:00'],
  ['jiro', 'osaka', '2020-02-11T12:00:00'],
  ['saburo', 'nara', '2020-03-22T12:00:00']
]
df = pd.DataFrame(data = data, index = ['a', 'b', 'c'], columns = ['name', 'address', 'birth'])
print(df.T)
```

![img123.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192873/3c066206-e5cf-172c-287c-8b71967e2598.png)


### Datarfame.at & Dataframe.iat 【単一の値の抽出】

atとiatを使うことで、**Dataframe内の任意の位置の値を取得することができます。**
**Dataframeのatとiatの場合は、引数は必ず2つ必要**です。(位置を一つに限定するため)
atとiatの違いは、atは行ラベル、列ラベルで位置を指定するのに対して、iatは行番号、列番号で指定します。

```python
data = [
  ['taro', 'kyoto', '2020-01-01T12:00:00'],
  ['jiro', 'osaka', '2020-02-11T12:00:00'],
  ['saburo', 'nara', '2020-03-22T12:00:00']
]
df = pd.DataFrame(data = data, index = ['a', 'b', 'c'], columns = ['name', 'address', 'birth'])
print(df.at['a','address']) # kyoto
```
上記のコードの場合は、「a」行の「address」列を指定しているので、kyotoを取得することができます。

![img123.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192873/92d0a524-5ac6-b380-8b73-eff5a2b559da.png)

iatを使った場合は以下のようになります。

```python
data = [
  ['taro', 'kyoto', '2020-01-01T12:00:00'],
  ['jiro', 'osaka', '2020-02-11T12:00:00'],
  ['saburo', 'nara', '2020-03-22T12:00:00']
]
df = pd.DataFrame(data = data, index = ['a', 'b', 'c'], columns = ['name', 'address', 'birth'])
print(df.iat[1,2]) #2020-02-11T12:00:00
```

![img123.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192873/cfed7f2a-ec52-d680-937a-1b229435d0fe.png)


「1」行目の「2」列目を指定しているので、2020-02-11T12:00:00を取得することができます。(行番号、列番号は0始まりです)


### Dataframe.loc & Dataframe.iloc 【行、列の抽出】

Dataframeで一番重要だといっても過言ではないほど頻出のlocについて説明します。
locの使い方を覚えておけば、基礎的な内容であればほぼ対応可能です。
locの基本的な構文は下記になります。
**スライスを使う場合は、配列で記述するとエラーになります。**

```
Dataframe.loc[[<行ラベル>], [列ラベル]]
Dataframe.loc[行ラベルA:行ラベルB, 列ラベルA:列ラベルB]
```

```python
data = [
  ['taro', 'kyoto', '2020-01-01T12:00:00'],
  ['jiro', 'osaka', '2020-02-11T12:00:00'],
  ['saburo', 'nara', '2020-03-22T12:00:00']
]
df = pd.DataFrame(data = data, index = ['a', 'b', 'c'], columns = ['name', 'address', 'birth'])
# 単一行の抽出
print(df.loc[['a']])

# 複数行の抽出
print(df.loc[['a', 'b']])

# 単一列の抽出
print(df.loc[:, ['name']])

# 複数列の抽出
print(df.loc[:, ['name', 'address']])

# 行と列の組み合わせ抽出
print(df.loc[['a', 'c'], ['name', 'birth']])
print(df.loc['a':'c', ['name', 'birth']])
```

行ラベルと列ラベルの部分を配列で書かなかった場合でも、1行の抽出、1列の抽出が可能ですが、配列で1行の抽出、1列の抽出をした場合と挙動が異なります。
(この仕様も混乱のもとなので気をつけてください、、、)

```python
data = [
  ['taro', 'kyoto', '2020-01-01T12:00:00'],
  ['jiro', 'osaka', '2020-02-11T12:00:00'],
  ['saburo', 'nara', '2020-03-22T12:00:00']
]
df = pd.DataFrame(data = data, index = ['a', 'b', 'c'], columns = ['name', 'address', 'birth'])
# 単一行の抽出
df.loc[['a']]
type(df.loc[['a']]) # pandas.core.frame.DataFrame

# 単一行の抽出
df.loc['a']
print(type(df.loc['a'])) # pandas.core.series.Series

# 単一列の抽出
df.loc[:, ['name']]
print(type(df.loc[:, ['name']])) # pandas.core.frame.DataFrame

# 単一列の抽出
df.loc[:, 'name']
print(type(df.loc[:, 'name'])) # pandas.core.series.Series
```

上記コードのように、配列で指定した場合はDataframeが返り、配列なしで指定した場合はSeriesが返ります。(**実際に使う場合は注意**)

### DataFrame.columns & DataFrame.index 【列ラベル、行ラベルの確認】

Dataframeにどのような列ラベル、行ラベルが設定されているのかを確認したい場面は多くあります。
columnsで列ラベル、indexで行ラベルの一覧を確認することができます。

```python
data = [
  ['taro', 'kyoto', '2020-01-01T12:00:00'],
  ['jiro', 'osaka', '2020-02-11T12:00:00'],
  ['saburo', 'nara', '2020-03-22T12:00:00']
]
df = pd.DataFrame(data = data, index = ['a', 'b', 'c'], columns = ['name', 'address', 'birth'])
# 列ラベルを表示
print(df.columns) # Index(['name', 'address', 'birth'], dtype='object')

# 行ラベルを表示
print(df.index) # Index(['a', 'b', 'c'], dtype='object')
```

### DataFrame.shape 【行数、列数を取得】

Dataframe内にデータが何行、何列存在しているのかを確認したいときも多くあります。
shapeを使うと、行数、列数を取得することができます。
shapeで帰ってきたタプルの一つ目が行数、二つ目が列数となります。

```python
data = [
  ['taro', 'kyoto', '2020-01-01T12:00:00'],
  ['jiro', 'osaka', '2020-02-11T12:00:00'],
  ['saburo', 'nara', '2020-03-22T12:00:00']
]
df = pd.DataFrame(data = data, index = ['a', 'b', 'c'], columns = ['name', 'address', 'birth'])
# 行数・列数を取得する
print(df.shape) # (3, 3)
```

### DataFrame.values 【numpy配列を取得する】

DataFrameには値のほかに行ラベル、列ラベルといった情報が付随していますが、ラベル情報は不要でデータのみ必要な場合、numpy配列に変換することができます。
※ただし、valuesでnumpy配列を取得する方法は非推奨のようです。(nkayさんありがとうございます。)
numpy配列を取得する場合は、to_numpy関数を使用して下さい。(詳細はコメント欄に記載)

```python
data = [
  ['taro', 'kyoto', '2020-01-01T12:00:00'],
  ['jiro', 'osaka', '2020-02-11T12:00:00'],
  ['saburo', 'nara', '2020-03-22T12:00:00']
]
df = pd.DataFrame(data = data, index = ['a', 'b', 'c'], columns = ['name', 'address', 'birth'])
#numpy配列を取得する
print(df.values)
```

![img123.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192873/35f73bc6-d4e8-4d2d-f2ef-488446512b09.png)

numpy配列に変換すると、通常の2次元配列と同じようなアクセル方法でデータを取得することができます。
**DataFrameとSeries、numpy配列の場合でそれぞれデータの抽出方法が異なるので注意してください。**
(これも混乱の元、、、、)

```python
# numpy配列は通常の2次元配列と同じようにデータにアクセスできる
# 0行1列目を抜き出す
print(df.values[0][1]) # kyoto

# 0行目を抜き出す
print(df.values[0])

# スライスも使えます
# 1列目を抜き出す
print(df.values[:, 1]) # ['kyoto' 'osaka' 'nara']
```

## 最後に

Pandasは確かに難しいのですが、公式のリファレンスがとても分かりやすく書かれていますので、リファレンスをしっかり読んで学習を進めれば理解するのは無茶苦茶ハードルが高い、というわけではないように思います(自分が思い込んでるだけかもですが、、、)。
特に、[10 minutes to pandas](https://pandas.pydata.org/pandas-docs/stable/getting_started/10min.html?highlight=missing)は内容がコンパクトに、わかりやすくまとまっているので、最初のとっかかりとしてはとてもおススメです。

この記事内で4回ほど「混乱の元」という記述を使っているかと思います。
その部分は特にややこしい部分ですので、しっかりと理解していないと、データの量が多くなった場合、データの結合などをする場合に対応できなくなる可能性があるのでご注意ください。

Part2ではいよいよPandasのメソッドについて紹介します。
メソッドも種類がめちゃくちゃ多くて、学習するのは大変なのですが、できるだけわかりやすく書くつもりですので、よろしくお願いします。(^^♪
では(^_-)-☆