---
file_format: mystnb
kernelspec:
  name: python3
  display_name: Python 3
---

# pandasでデータ加工（45分）

この章では、pandasを使って表形式のデータを読み込み、加工し、集計する流れを体験します。

```{note}
入力が遅れた場合などに、{nb-download}`この章のNotebook <5_pandas.ipynb>`をダウンロードできます。ダウンロードしたファイルを作業フォルダに置き、JupyterLabのファイルブラウザでダブルクリックして開いてください。
ダウンロードしたNotebookには、この章の説明の文章も入っています。図などの一部の書き方は、JupyterLabでは記号のまま表示されます。

自分で作った`5_pandas.ipynb`と同じ名前のため、上書きしないように注意してください。
```

## pandasとは

**pandas**はオープンソースのデータ分析と加工のためのライブラリです。
表形式のデータを読み込み、加工、集計といった作業ができます。
Excelのような行と列を持つ表のデータを、Pythonのコードで扱えます。
CSVファイルなどからデータを読み込み、必要な行や列を取り出したり、抜けている値を補ったり、複数の表をつなげたり、グループごとに集計したりできます。

* pandas公式サイト：<https://pandas.pydata.org/>

第4章の{ref}`jupyter-start`の手順で、作業フォルダでJupyterLabを起動してください。
新しいNotebookを作成し、名前を`5_pandas.ipynb`に変更します。

最初のセルでpandasをインポートします。
pandasは`pd`という別名でインポートするのが慣例です。

```{code-cell} ipython3
import pandas as pd
```

## SeriesとDataFrame

pandasでは、主にSeriesとDataFrameの2種類の形でデータを扱います。

**Series**は、1次元のデータです。
値の並びに、**インデックス**というラベルが付いています。
次のコードでは、商品名をインデックス、単価を値としたSeriesを作ります。

```{code-cell} ipython3
prices = pd.Series([400, 450, 500], index=["コーヒー", "紅茶", "ケーキ"])
prices
```

左側の商品名がインデックス、右側の数値が値です。
最後の行の`dtype`は値のデータ型で、`int64`は整数を表します。
インデックスは`index`で確認できます。

```{code-cell} ipython3
prices.index
```

**DataFrame**は、行と列を持つ表です。
列名と列の値を辞書で渡して作ります。
DataFrameの各列はSeriesです。

```{code-cell} ipython3
items = pd.DataFrame({"item": ["コーヒー", "紅茶", "ケーキ"], "price": [400, 450, 500]})
items
```

DataFrameの行にもインデックスが付きます。
インデックスを指定せずに作ると、0から始まる番号（`RangeIndex`）になります。

```{code-cell} ipython3
items.index
```

列名は`columns`で確認できます。

```{code-cell} ipython3
items.columns
```

## CSVファイルの書き出しと読み込み

**CSVファイル**は、表形式のデータを保存するテキストファイルです。
表の1行をファイルの1行に書き、値をカンマ（`,`）で区切って並べます。
多くのツールで読み書きできるため、表形式データの保存によく使われるフォーマットです。

DataFrameは`to_csv()`メソッドでCSVファイルに書き出せます。
`index=False`を指定すると、インデックスを書き出しません。

```{code-cell} ipython3
items.to_csv("items.csv", index=False)
```

上のコードを実行すると作業フォルダに`items.csv`ができます。
ファイルの中身は次のようなテキストです。
1行目が列名で、2行目以降が表の各行です。

```text
item,price
コーヒー,400
紅茶,450
ケーキ,500
```

JupyterLabのファイルブラウザで`items.csv`をダブルクリックすると、表形式で開きます。
左端の番号は表示のための行番号で、DataFrameのインデックスではありません。

```{figure} images/5_items_csv.png
:alt: items.csvを開いたJupyterLabの画面

`items.csv`を開いたJupyterLabの画面
```

CSVファイルは`pd.read_csv()`関数で読み込みます。
読み込んだ表は、書き出す前の`items`と同じになります。

```{code-cell} ipython3
items_csv = pd.read_csv("items.csv")
items_csv
```

## 売上データを読み込む

この節からは、架空の店舗の売上データを使います。
2026年4月1日から30日までについて、店舗ごと、商品ごとの毎日の販売数を記録したデータです（実在の店舗とは関係ありません）。
データには次の列があります。

```{table} 売上データの列
| 列名 | 内容 |
|---|---|
| `date` | 日付 |
| `store` | 店舗 |
| `item` | 商品 |
| `quantity` | 販売数 |
| `price` | 単価 |
```

次の2つのファイルをダウンロードし、作業フォルダの直下（`5_pandas.ipynb`と同じ場所）に置いてください。

* {download}`sales.csv`：売上データ（この節で使います）
* {download}`sales_next_month.csv`：翌月の売上（「データを連結する」で使います）

```{note}
`sales.csv`の中身は、JupyterLabで開いて確認してください。
Excelで開くと文字化けすることがあります。
```

`pd.read_csv()`関数で売上データを読み込みます。
`parse_dates`に列名を指定すると、その列を日付の型として読み込みます。
第6章で日付を横軸にしたグラフを描くため、ここで日付の型にしておきます。

```{code-cell} ipython3
df = pd.read_csv("sales.csv", parse_dates=["date"])
```

`FileNotFoundError`と表示された場合は、ファイルが見つからないことを示しています。
`sales.csv`が作業フォルダの直下にあるか、ファイル名が`sales (1).csv`などに変わっていないかを確認してください。

続けて、読み込んだデータの概要を確認します。
加工する前に、どのような列があるか、何行あるか、値の抜けがないかなどを確かめます。

`df`だけを実行すると、DataFrameが表示されます。
行が多い場合は、途中の行が「...」で省略され、先頭と末尾の行だけが表示されます。
表の下には、行の数と列の数が表示されます。

```{code-cell} ipython3
df
```

`head()`メソッドは先頭の5行を表示します。

```{code-cell} ipython3
df.head()
```

同様に、`tail()`メソッドは末尾の5行を表示します。

```{code-cell} ipython3
df.tail()
```

`describe()`メソッドは、数値の列の統計（件数、平均、標準偏差、最小、最大など）を表示します。

```{code-cell} ipython3
df.describe()
```

日付の列（`date`）も表示され、最小（`min`）と最大（`max`）でデータの期間が分かります。

```{table} describe()メソッドの出力の意味
| 項目 | 意味 |
|---|---|
| `count` | データの件数（欠損値（後述）などは件数から除外される） |
| `mean` | 平均値 |
| `min` | 最小値 |
| `25%` | 小さい方から25%の位置の値 |
| `50%` | 中央値（小さい方から50%の位置の値） |
| `75%` | 小さい方から75%の位置の値 |
| `max` | 最大値 |
| `std` | 標準偏差（値のばらつきの大きさ） |
```

`shape`は、行の数と列の数を表示します。

```{code-cell} ipython3
df.shape
```

## データを抽出する

```{note}
コードを実行した結果が資料と違うときやエラーになったときは、「売上データを読み込む」のセルから順に実行し直してください。
```

DataFrameから、必要な列や行だけを取り出します。

列名を`[]`で指定すると、1つの列をSeriesとして取り出せます。

```{code-cell} ipython3
df["store"]
```

出力の最後の`dtype: str`は、この列の型が文字列型（`str`）であることを表します。
pandas 3.0からは文字列の列が`str`型と表示されます（それ以前のバージョンでは`object`型と表示されます）。

列名のリストを指定すると、複数の列をDataFrameとして取り出せます。

```{code-cell} ipython3
df[["store", "quantity"]]
```

`[]`に`開始:終了`の形でスライスと同じ表記を指定すると、行を範囲で取り出せます。
スライスと同様、開始の行は含まれ、終了の行は含まれません。
開始や終了の数値を省略すると、それぞれ先頭から、末尾までを取り出します。
次のコードでは、先頭の2行（0行目と1行目）を取り出します。

```{code-cell} ipython3
df[:2]
```

### locとiloc

行と列を指定して取り出すには`loc`や`iloc`を使います。
どちらも`[行, 列]`の形で指定し、`:`で範囲を指定できます。

`loc`は、インデックスのラベルと列名で指定します。
次のコードでは、インデックスが0から2までの行の、`store`から`quantity`までの列を取り出します。
`loc`では、範囲の終わりのラベル（2と`quantity`）も含まれます。

```{code-cell} ipython3
df.loc[0:2, "store":"quantity"]
```

`iloc`は、行のインデックス番号と列のカラム番号で指定します。どちらも0から始まる連番です。
次のコードでは、0行目と1行目の、1列目から3列目（`store`、`item`、`quantity`）を取り出します。
`iloc`では、`[]`の範囲の指定と同じく、範囲の終わりは含まれません。

```{code-cell} ipython3
df.iloc[0:2, 1:4]
```

### 条件で抽出

列に対して条件を書くと、行ごとに条件を満たすかが`True`または`False`で返されます。

```{code-cell} ipython3
df["store"] == "東京店"
```

この条件をDataFrameの`[]`に入れると、`True`の行だけを抽出できます。
次のコードでは、東京店の行だけを取り出して`df_tokyo`に代入します。

```{code-cell} ipython3
df_tokyo = df[df["store"] == "東京店"]
df_tokyo.head(10)
```

取り出したDataFrameのインデックスは、元となったDataFrameのインデックス値のままで、間が飛ばされていることがわかります。
そのため、`df_tokyo`では`loc`と`iloc`に同じ数字を指定しても、違う行が選ばれます。
`loc`ではインデックスが9の行が選ばれます。

```{code-cell} ipython3
df_tokyo.loc[9]
```

`iloc`では上から10番目（0から数えて9番目）の行が選ばれます。

```{code-cell} ipython3
df_tokyo.iloc[9]
```

## データを整形する

分析しやすいように、データの並び順や値を整えます。

`sort_values()`メソッドは、指定した列の値で行を並べ替えます。
デフォルトでは昇順で並べ替えが行われ、`ascending=False`を指定すると降順に並べ替えます。
次のコードでは、販売数の降順（多い順）に並べ替えを行い、その先頭5行を表示します。

```{code-cell} ipython3
df.sort_values("quantity", ascending=False).head()
```

並べ替えた結果を変数に代入していないため、元の`df`の並び順は変わりません。

### 欠損値の処理

値が入っていないことを**欠損値**と呼びます。
実際のデータでは、記録漏れなどで欠損値が含まれることがよくあります。
欠損値があると計算の結果が正しくならないため、分析の前に確認して対処します。

`isna()`メソッドは、値が欠損値かどうかを`True`または`False`で返します。
続けて`sum()`メソッドを使うと、列ごとに欠損値の数を数えられます。
なお、`isnull()`メソッドは`isna()`メソッドと同じ働きをします。

```{code-cell} ipython3
df.isna().sum()
```

`quantity`の列に欠損値があります。
欠損値のある行を取り出して確認します。

```{code-cell} ipython3
df[df["quantity"].isna()]
```

欠損値は`NaN`と表示されます。
`NaN`はNot a Numberの略で非数を表します。
並べ替えの結果で`quantity`が`40.0`のように小数で表示されていたのは、欠損値があるためです。
欠損値を含む列は、整数の列でも小数の型になります。

欠損値の対処には、欠損値が存在する行を削除する方法と、欠損値をほかの値に置き換える方法があります。
`dropna()`メソッドは、欠損値のある行を削除します。
ここでは行の数が減ることを確かめるだけで、元の`df`は変えません。

```{code-cell} ipython3
df.dropna().shape
```

ここでは、販売数の欠損値を0に置き換えます。
`fillna()`メソッドで欠損値を0に置き換え、その結果を`quantity`列に代入します。

```{code-cell} ipython3
df["quantity"] = df["quantity"].fillna(0)
```

```{note}
書籍やWebの記事には、`df["quantity"].fillna(0, inplace=True)`のように`inplace=True`を指定する例があります。
pandas 3.0では、この書き方では元の`df`が変わりません。
この節のように、結果を列に代入してください。
```

欠損値がなくなったので、`astype()`メソッドで`quantity`列を整数（`int`）型に変換します。

```{code-cell} ipython3
df["quantity"] = df["quantity"].astype(int)
```

欠損値だった2つの行を`loc`で指定し、`quantity`列の値が0に置き換わったことを確かめます。

```{code-cell} ipython3
df.loc[[23, 69]]
```

最後に、販売数と単価を掛けて、売上金額の列（`amount`）をDataFrameに追加します。
列どうしの計算は全ての行に対して行われ、全ての`amount`列に計算結果が入ります。

```{code-cell} ipython3
df["amount"] = df["quantity"] * df["price"]
df.head()
```

## データを連結する

ここでは、複数のDataFrameを1つにまとめる**連結**について説明します。

「売上データを読み込む」でダウンロードした翌月の売上（`sales_next_month.csv`）を読み込み`df_next_month`に代入します。
CSVファイルには2026年5月1日の売上が3行入っています。

```{code-cell} ipython3
df_next_month = pd.read_csv("sales_next_month.csv", parse_dates=["date"])
df_next_month
```

`FileNotFoundError`と表示された場合は、「売上データを読み込む」と同じく、ファイルの場所と名前を確認してください。

`pd.concat()`関数にDataFrameのリストを渡すと、指定されたDataFrameを行方向に連結します。
`ignore_index=True`を指定すると、インデックスを0から振り直します。

```{code-cell} ipython3
pd.concat([df, df_next_month], ignore_index=True).tail()
```

売上の後ろに翌月の売上の3行が連結されました。
翌月の売上には`amount`の列がないため、`amount`は欠損値になります。
この結果は後の節では使わないため、代入していません。

`axis=1`を指定すると、DataFrameを列方向に連結します。
例として、売上の先頭の3行を`date`と`store`の列、`item`と`quantity`の列の2つのDataFrameに分けます。

```{code-cell} ipython3
left = df[["date", "store"]].head(3)
left
```

```{code-cell} ipython3
right = df[["item", "quantity"]].head(3)
right
```

2つのDataFrameを横に連結すると、元の4列の表に戻ります。

```{code-cell} ipython3
pd.concat([left, right], axis=1)
```

## データを集計する

**集計**は、データをグループごとにまとめて、合計や平均などを計算することです。
`groupby()`メソッドに列名を指定すると、その列の値ごとにグループに分けます。
次のコードでは、店舗ごとにグループ化し、売上金額（`amount`）を`sum()`メソッドで合計します。

```{code-cell} ipython3
df.groupby("store")["amount"].sum()
```

## さまざまなファイル形式の読み込みと書き出し

pandasは、CSVファイルのほかにもさまざまなファイル形式の読み込みと書き出しに対応しています。
読み込みには`pd.read_csv()`関数のような`read_`からはじまるpandasの関数を、書き出しには`to_csv()`メソッドのような`to_`からはじまるDataFrameのメソッドを使います。
ファイル形式によっては、pandasのほかに追加のライブラリのインストールが必要です。

```{table} 代表的なファイル形式と読み込み・書き出しの関数・メソッド
| ファイル形式 | 読み込み | 書き出し | 追加のライブラリ |
|---|---|---|---|
| CSV | `pd.read_csv()` | `to_csv()` | 不要 |
| Excel | `pd.read_excel()` | `to_excel()` | openpyxl |
| JSON | `pd.read_json()` | `to_json()` | 不要 |
| Parquet | `pd.read_parquet()` | `to_parquet()` | pyarrow |
| HTML | `pd.read_html()` | `to_html()` | 読み込みにlxmlなど |
```

最後に、加工したデータを`to_csv()`メソッドでCSVファイルに書き出します。
このファイルは第6章でグラフを作るときに使います。

```{code-cell} ipython3
df.to_csv("sales_clean.csv", index=False)
```

作業フォルダに`sales_clean.csv`ができます。
この章のコードを実行できなかった場合は、{download}`sales_clean.csv`をダウンロードして作業フォルダに置いてください。

## この章のまとめ

この章では以下を行いました。

```{table} この章で行ったこと
| 節 | 行ったこと | 使った関数・メソッドなど |
|---|---|---|
| SeriesとDataFrame | SeriesとDataFrameの作成、インデックスと列名の確認 | `pd.Series()`、`pd.DataFrame()`、`index`、`columns` |
| CSVファイルの書き出しと読み込み | DataFrameのCSVファイルへの書き出しと読み込み | `to_csv()`、`pd.read_csv()` |
| 売上データを読み込む | 売上データの読み込みと概要の確認 | `pd.read_csv()`、`head()`、`tail()`、`describe()`、`shape` |
| データを抽出する | さまざまな方法で指定した列や行を抽出 | `[]`、`loc`、`iloc` |
| データを整形する | 並べ替え、欠損値の処理、列の追加 | `sort_values()`、`isna()`、`dropna()`、`fillna()`、`astype()` |
| データを連結する | DataFrameを行方向または列方向に連結 | `pd.concat()` |
| データを集計する | 店舗ごとの売上金額の集計 | `groupby()`、`sum()` |
| さまざまなファイル形式の読み込みと書き出し | ファイル形式ごとの読み込みと書き出しの関数・メソッドの確認 | `pd.read_excel()`、`to_excel()`など |
```

次の第6章では、書き出した`sales_clean.csv`を使って、Matplotlibでさまざまなグラフを描画します。
