pandasを使った主要フォーマットファイル操作網羅

Pandasを用いて様々なファイル形式の、読み込みと書き込みの基本操作をまとめます.

# 目次
- [環境](#環境)
- [Excelファイルの操作](#excelファイルの操作)
- - [openpyxlについて](#openpyxlについて)
- - [Excelファイルの読み込み](#excelファイルの読み込み)
- - - [read_excel関数の主なプロパティー](#read_excel関数の主なプロパティー)
- - [Excelファイルへの書き込み](#excelファイルへの書き込み)
- - - [pandas.DataFrame.to_excelを使用する方法](#pandasdataframeto_excelを使用する方法)
- - - [pandas.ExcelWriterを使用する方法](#pandasexcelwriterを使用する方法)
- [固定長ファイルの操作](#固定長ファイルの操作)
- - [固定長ファイルの読み込み](#固定長ファイルの読み込み)
- - [固定長ファイルへの書き込み](#固定長ファイルへの書き込み)
- [CSVファイルの操作](#jsonファイルの操作)
- - [CSVファイルの読み込み](#csvファイルの読み込み)
- - [CSVファイルへの書き込み](#csvファイルへの書き込み)
- [JSONファイルの操作](#jsonファイルの操作)
- - [JSONファイルの読み込み](#jsonファイルの読み込み)
- - - [シンプルな構造のJSONファイルの読み込み](#シンプルな構造のjsonファイルの読み込み)
- - - [ネスト構造のJSONファイルの読み込み](#ネスト構造のjsonファイルの読み込み)
- - [JSONファイルへの書き込み](#jsonファイルへの書き込み)
# 環境
Python 3.11.9
pandas 2.2.3
openpyxl 3.1.5

下記のコマンドでインストールできます。
```
pip install pandas openpyxl
```
# Excelファイルの操作
## openpyxlについて

PythonでExcelファイル（特に.xlsx形式）を操作するためのライブラリです。
Pandasはデータ分析用のライブラリであり、Excelファイルを扱う際には```read_excel関数```を使用します。この関数は内部でopenpyxlを利用して.xlsxファイルを読み込むため、Pandasを使用する場合はopenpyxlが必要となります。

[公式ドキュメント](https://openpyxl.readthedocs.io/en/stable/)

## Excelファイルの読み込み

PandasでExcelファイルを読み込む際には、```read_excel関数```を使用します。引数に読み込みたいExcelファイルのパスを指定すると、戻り値として```DataFrameオブジェクト```（単一のシートを読み込む場合） または ```DataFrameオブジェクト の辞書```（複数のシートを読み込む場合）が返ってきます。

```sample.py
import pandas

# 読み込みたいExcelファイルのパス
READ_EXCEL_PATH = r".\sample.xlsx"

# パスを引数に指定
df = pandas.read_excel(READ_EXCEL_PATH)

print(type(df)) # <class 'pandas.core.frame.DataFrame'>
```

複数シートを読み込みたい（読み込むシートを指定したい）時は、```sheet_name```パラメータをlist形式で指定します。```None```を指定することで、全てのシートを読み込みます。
```sample.py
# 配列で指定すると複数シート読み込み
df_dict = pandas.read_excel(READ_EXCEL_PATH, sheet_name=["Sheet1", "Sheet2"])
print(df_dict)
print(type(df_dict)) # <class 'dict'>
```

### read_excel関数の主なパラメータ

```sample.py
# ヘッダー行の指定 
# 2行目をヘッダーとして使用
df = pandas.read_excel(READ_EXCEL_PATH, header=1)

# 読み込む列の指定　
# 1列目から4列目（0, 1, 2, 3列）を読み込む
df = pandas.read_excel(READ_EXCEL_PATH, usecols=[0, 1, 2, 3])

# 列文字での指定　
# A列とC列からF列を読み込む
df = pandas.read_excel(READ_EXCEL_PATH, usecols="A,C:F")

# 特定の行をスキップ（除外）
# skiprows=3で最初の3行を、skipfooter=3で最後3行を読み込まない
df = pandas.read_excel(READ_EXCEL_PATH, skiprows=3, skipfooter=3)
# 1行目と3行目をスキップ
df = pandas.read_excel(READ_EXCEL_PATH, skiprows=[0, 2])

# インデックスを指定 
# index_col=0を指定することで、最初の列がインデックスとして使用され、データの一部ではなる
df = pandas.read_excel(READ_EXCEL_PATH, index_col=0)

# ヘッダ名称で日付列を指定
df = pandas.read_excel(READ_EXCEL_PATH, header=0, parse_dates=["日付"], sheet_name="売上")
print(df["日付"].dtype) # datetime64[ns]
```

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_excel.html

## Excelファイルへの書き込み
Excelファイルに書き込む方法として、2パターン紹介します

##### pandas.DataFrame.to_excelを使用する方法

**■ 特徴**
* DataFrameをExcelファイルに書き込むためのメソッドです
* 指定したパスにファイルが存在しない場合は、生成されます
* 既に存在するファイル名を指定すると、既存のファイルのその内容を消去して上書きします
* シンプルな使い方ができ、単一のシートへの書き込みに適しています

```sample.py
import pandas

WRITE_EXCEL_PATH = r".\output.xlsx"

# 書き込みたいデータフレームを定義
df = pandas.DataFrame(
    [["リンゴ", 100, 150], ["バナナ", 80, 100], ["オレンジ", 50, 120], ["ぶどう", 30, 300], ["イチゴ", 20, 500]],
    index=["F001", "F002", "F003", "F004", "F005"],
    columns=["果物名", "在庫数", "価格"],
)

# データフレームをExcelファイルに書き込む
df.to_excel(WRITE_EXCEL_PATH, sheet_name="果物管理", index=False)

# 指定したカラムのみ書きむ
df.to_excel(WRITE_EXCEL_PATH, sheet_name="果物管理", columns=["果物名", "在庫数"])

```
**■ 主なパラメータ**
<table> 
  <thead>
    <tr>
      <th>パラメータ　</th> <th>説明</th>
    </tr>
  </thead>
  <tr>
    <td> index </td> <td>データフレームのインデックスをExcelに書き込むかどうかを指定。<br/>デフォルトはTrue</td>
  </tr>
  <tr>
    <td> header </td> <td>列名（ヘッダー）を出力するかどうかを指定。ヘッダーに該当するのは「columns」。デフォルトはTrue</td>
  </tr>
   <tr>
    <td> columns</td> <td>出力する列をリストで指定。このパラメータを指定すると、特定の列のみを選択して書き込むことが可能。</td>
  </tr>
  <tr>
    <td> startrow </td> <td>Excelシート内でデータを書き始める行番号を指定。<br/>デフォルトは0（最初の行）</td>
  </tr>
  <tr>
    <td> startcol </td> <td>Excelシート内でデータを書き始める列番号を指定。<br/>デフォルトは0（最初の列）</td>
  </tr>
</table>

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_excel.html

##### pandas.ExcelWriterを使用する方法
**■ 特徴**
* Excelファイルへの書き込みを管理するためのオブジェクトです
* 複数のデータフレームを異なるシートに書き込む場合や、既存のExcelファイルに追記する際に非常に便利です
* ```with文```と組み合わせて使用することで、リソース管理が容易になり、複数シートへの出力が可能になります
* 指定したパスにファイルが存在しない場合は生成されます
* modeパラメータとif_sheet_existsパラメータを設定することにより、既存のExcelファイルに追記などが可能になります

```samole.py
# 書き込みたいデータフレームを定義
df_vegetables = pandas.DataFrame(
    {"野菜名": ["キャベツ", "人参", "トマト"], "在庫数": [100, 80, 20], "価格": [300, 100, 200]}
)
df_fruit = pandas.DataFrame(
    {"果物名": ["リンゴ", "バナナ", "オレンジ"], "在庫数": [10, 50, 40], "価格": [150, 130, 100]}
)

# ExcelWriterを使用して複数シートに書き込む
# mode="a"で追記　if_sheet_exists="overlay"で既存のシートが存在した場合の動作を指定
with pandas.ExcelWriter(WRITE_EXCEL_PATH, mode="a", if_sheet_exists="overlay") as writer:
    df_vegetables.to_excel(writer, sheet_name="野菜", index=False)
    df_fruit.to_excel(writer, sheet_name="果物", index=False)
```

**■ 主なパラメータ**
<table> 
  <thead>
    <tr>
      <th>パラメータ　</th> <th>説明</th>
    </tr>
  </thead>
  <tr>
    <td> mode </td> <td>Excelファイルへの書き込みモードを指定。<br/>デフォルトは'w'(書き込み専用)、既存のファイルに追記したい場合は'a'（追記）を指定。</td>
  </tr>
  <tr>
    <td>if_sheet_exists</td>
    <td>シートが既に存在する場合の挙動を制御。<br/>
    ・error: エラーを発生させる（デフォルト）<br/>
    ・new: 新しいシート名を生成して追加<br/>
    ・replace: 既存シートを削除して新規作成<br/>
    ・overlay: 既存シートに上書き<br/>
    </td>
  </tr>
</table>

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.ExcelWriter.html



# 固定長ファイルの操作
**■ 固定長ファイルとは**
固定長ファイルは、各レコードの長さが一定であり、各フィールドが決まった位置に配置される形式のテキストファイルです。1行が1つのレコードを表し、各レコードは複数のフィールドで構成されており、フィールドはそれぞれ固定された長さで区切られます。CSV形式などに比べて、高速にデータ取得が可能な点などが、メリットです。

```サンプルの固定長ファイル.txt
顧客ID,名前,性別,生年月日,入金金額
    1 ｽｽﾞｷM19800101  11000
    2 ﾏﾂﾓﾄF19900202   1000
    3 ﾀｶﾊｼM20000303   5000
    4 ﾅｶﾑﾗF19850115    300
    5  ｱﾍﾞM19950320  25000

shift-jisエンコード
```
## 固定長ファイルの読み込み
固定長ファイルを読み込む際は、```pandas.read_fwf関数```を使用します。
```sample.py
import pandas

# ファイルパスの指定
READ_FILE_PATH = "固定長sample.txt"

# 各フィールドの開始位置と幅を指定
# shift-jisエンコードで半角カナは1文字１バイト
colspecs = [
    (0, 5),  # 顧客ID: 5バイト
    (5, 10),  # 名前: 5バイト
    (10, 11),  # 性別: 1バイト（MまたはF）
    (11, 19),  # 生年月日: 8バイト
    (19, 26),  # 入金額: 7バイト（固定長）
]

# 固定長ファイルの読み込み
df = pandas.read_fwf(
    READ_FILE_PATH,
    colspecs=colspecs,
    encoding="shift-jis",
    header=None
)

# カラム名の設定
df.columns = ["顧客ID", "氏名", "性別", "生年月日", "入金額"]

# 読み込んだデータの表示
print(df)
```
**■ 主なパラメータ**
<table> 
  <thead>
    <tr>
      <th>パラメータ　</th> <th>説明</th>
    </tr>
  </thead>
  <tr>
    <td> colspecs </td> <td>各フィールドの開始位置と幅をタプル(int,int)として指定。</td>
  </tr>
  <tr>
    <td>encoding</td>
    <td>エンコード形式を指定</td>
  </tr>
   <tr>
    <td>header</td>
    <td>ヘッダー行の有無を指定。デフォルトでは、1行目がヘッダーとなる</td>
  </tr>
</table>

**■ colspecsの役割**
colspecsは、各フィールドの開始位置と終了位置を指定するために使用されます。これにより、Pandasはどの部分をどのカラムにマッピングするかを決定するので、重要です。デフォルトでは、```colspecs=infer```となり、最初の100行を解析し、カラムの幅を推測します。そのため、意図しないデータ抽出となる可能性が高いので、明示的に指定することが推奨されます。

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_fwf.html#pandas-read-fwf

## 固定長ファイルへの書き込み
pandasに、固定長ファイルへの出力をサポートする関数は記事投稿時点ではありませんでした。
下記は自作ではありますが```DataFrame型```を固定長ファイルに出力するサンプルコードです。要件に合わせて、自作する必要があります。
```sample.py
ENCODING = "shift-jis"
# サンプルデータフレーム
df = pandas.DataFrame(
    [
        # 顧客ID,名前,性別,生年月日,金額
        [11, "ｷﾑﾗ", "M", 20120909, 11000],
        [12, "ｻｻｷ", "F", 20020512, 100000],
        [13, "ｻｶﾓﾄ", "M", 20000303, 160000],
        [14, "ｲｶﾞﾗｼ", "F", 19991115, 11000],
        [15, "ﾊﾏﾀﾞ", "M", 19950320, 11000],
    ]
)


# データの各カラムを固定長で書き込み
def write_file(df, filename, widths):
    with open(filename, "w", encoding=ENCODING) as f:
        # DataFrameの各行に対して処理を行う
        for index, row in df.iterrows():
            line = "".join(f"{str(value):>{width}}" for value, width in zip(row, widths))
            f.write(line + "\n")


column_widths = [5, 5, 1, 8, 7]  # 各カラムの幅を指定
write_file(df, "output.txt", column_widths)
```

# CSVファイルの操作
## CSVファイルの読み込み
CSVファイルを読み込む際は、```pandas.read_csv 関数```を使用します。
```sample.py
import pandas

# 読み込みたいファイルのパス
READ_FILE_PATH = r".\sample.csv"

df = pandas.read_csv(READ_FILE_PATH)
print(df)
```

**■ 大規模なCSVファイルの読み込み**
大規模なCSVファイルを処理するために```chunksize```パラメータを使用することで、効率的にCSVファイルを読み込み、処理することも可能です。
```chunk```とは「大きな塊」という意味を持ち、大規模データを分割したひとまとまりの断片を指します。

```sample.py
import pandas

# 読み込みたいファイルのパス
READ_FILE_PATH = r".\large_data.csv"
# チャンクサイズを指定
CHUNK_SIZE = 100000
# 処理されたチャンクをまとめるlist
results = []

def calculate_average(chunk):
    # chunkに対して、「入金額」列の平均値を計算する
    return chunk["入金額"].mean()


for chunk in pandas.read_csv(READ_FILE_PATH, chunksize=CHUNK_SIZE, header=0):
    result = calculate_average(chunk)
    print(result)
    results.append(result)

print(results)
# 全チャンクの結果を集計
final_result = sum(results) / len(results)
print(f"全体の平均: {final_result}")

```
**■ 出力結果**　(100万件のCSVデータの処理結果)
```
1:501539.41211
2:498556.5636
3:499330.59795
4:499743.16578
5:500456.37189
6:500491.45455
7:501505.33551
8:499123.46885
9:500569.98155
10:499867.45092
results:[np.float64(501539.41211), np.float64(498556.5636), np.float64(499330.59795), np.float64(499743.16578), np.float64(500456.37189), np.float64(500491.45455), np.float64(501505.33551), np.float64(499123.46885), np.float64(500569.98155), np.float64(499867.45092)]
全体の平均: 500118.380271
```

```chunk```に分けて処理することで、メモリ使用量を抑えながら大量のデータを分析することができます

https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html

## CSVファイルへの書き込み
CSVファイルへ下記込む際は、```pandas.DataFrame.to_csv関数```を使用します。

```sample.py
import pandas

# 読み込みたいファイルのパス
WRITE_FILE_PATH = r".\output.csv"
# データを辞書形式で定義
data = {
    "顧客ID": ["C000001", "C000002", "C000003", "C000004", "C000005"],
    "名前": ["伊藤 真綾", "石川 明美", "鈴木 千代", "伊藤 花子", "松本 零"],
    "入金額": [723575, 550149, 910995, 26118, 956292],
    "入金日": ["20240825", "20240407", "20240531", "20240816", "20241023"],
}

# DataFrameを初期化
df = pandas.DataFrame(data)

df.to_csv(WRITE_FILE_PATH, index=False)
```

**■ modeパラメータの指定**
modeプロパティを設定することにより、すでにCSVファイルが存在していた場合の動作を制御することができます。

* **mode=w** :存在していた場合、一度削除 (デフォルト)
* **mode=a** :ファイルが存在する場合はファイルの末尾に追加
* **mode=x** :ファイルがすでに存在する場合は失敗

https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_csv.html

# JSONファイルの操作
## JSONファイルの読み込み
#### シンプルな構造のJSONファイルの読み込み

シンプルなJSONデータは、```read_json関数```を使用して簡単にDataFram変換できます。
```sample.json
[
    {
        "id": 1,
        "name": "Alice",
        "age": 30
    },
    {
        "id": 2,
        "name": "Bob",
        "age": 25
    },
    {
        "id": 3,
        "name": "John",
        "age": 35
    }
]

```
```sample01.py
import pandas

# ファイルパスの指定
READ_FILE_PATH = "json_sample.json"

df = pandas.read_json(READ_FILE_PATH)
print(df)
```
**■ 出力結果**
```
   id   name  age
0   1  Alice   30
1   2    Bob   25
2   3   John   35
```


一方、ネストされたJSON構造のファイルを読み込む際には注意が必要です。
```sample.json
{
    "users": [
        {
            "id": 1,
            "contact": {
                "email": "alice@example.com",
                "phone": "123-456-7890"
            }
        },
        {
            "id": 2,
            "contact": {
                "email": "bob@example.com",
                "phone": "234-567-8901"
            }
        }
    ]
}
```

上記のネストしたJSONを```sample01.py```で読み込んだ際の出力は下記のようになります。
**■ 出力結果**
```
                                                                                  users
0  {'id': 1, 'contactDetails': {'email': 'alice@example.com', 'phone': '123-456-7890'}}
1    {'id': 2, 'contactDetails': {'email': 'bob@example.com', 'phone': '234-567-8901'}}
```

この場合、読み込み後のDataFramを操作が難しくなったり、可読性の低下などがあります。
その解決策として、```json_normalize関数```を使用し、フラット化させる読み込み方法があります。

#### ネスト構造のJSONファイルの読み込み

```sample02.py
import pandas
import json

# JSONファイルを開いてデータを読み込む
with open(READ_FILE_PATH, "r", encoding="utf-8") as file:
    data = json.load(file)

# pandasのDataFrameに変換
df = pandas.json_normalize(data["users"])

print(df)
```

**■ 出力結果**
```
   id contactDetails.email contactDetails.phone
0   1    alice@example.com         123-456-7890
1   2      bob@example.com         234-567-8901
```

```json_normalize関数```は、ネストされたJSONオブジェクトをフラットな形式に変換するために使用されます。下記の例では、```data["users"]```からユーザー情報を抽出し、各フィールドが独立したカラムとして展開されます。フラットな形式にすることで、データ分析や可視化が容易になります

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_json.html

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.json_normalize.html

## JSONファイルへの書き込み
JSONファイルに書き込む際は、```pandas.DataFrame.to_json関数```を使用します。
引数に、出力先のパスを指定しない場合は、JSON形式の文字列に変換に変換されます。

```sample.py
import pandas as pd

WRITE_FILE_PATH = r".\output.json"
# サンプルデータフレーム
df = pd.DataFrame({"id": [1, 2, 3], "name": ["Alice", "Bob", "John"], "age": [30, 25, 35]})
# output.jsonファイルが出力される
df.to_json(WRITE_FILE_PATH)

# DataFrameをJSON形式の文字列に変換
json_data = df.to_json()
# {"id":{"0":1,"1":2,"2":3},"name":{"0":"Alice","1":"Bob","2":"John"},"age":{"0":30,"1":25,"2":35}}
print(json_data) 
```
**■ orientパラメータ**
```orien```パラメータを指定することで、出力されるJSONの出力形式を指定することができます。

**■ orienパラメータの種類**
<table> 
  <thead>
    <tr>
      <th>orienパラメータ　</th> <th>構造</th>
    </tr>
  </thead>
  <tr>
    <td> split </td> 
    <td>{index -> [index], columns -> [columns], data -> [values]}</td>
  </tr>
  <tr>
    <td>records</td>
    <td>[{column -> value}, ...]</td>
  </tr>
   <tr>
    <td>index</td>
    <td>{index -> {column -> value}}</td>
  </tr>
    <tr>
    <td> columns(デフォルト) </td> 
    <td>{column -> {index -> value}}</td>
  </tr>
  <tr>
    <td>values</td>
    <td>値のリスト</td>
  </tr>
   <tr>
    <td>table</td>
    <td> {'schema': {schema}、'data': {data}} のような辞書</td>
  </tr>
</table>

```sample.py
# サンプルデータフレーム
df = pd.DataFrame({"id": [1, 2, 3], "name": ["Alice", "Bob", "John"], "age": [30, 25, 35]})
# output.jsonファイルが出力される
df.to_json(WRITE_FILE_PATH)

# DataFrameをJSON形式の文字列に変換
json_data = df.to_json()

# 各orientでJSONに変換して表示
print(f"split:\n{df.to_json(orient='split')}\n")
print(f"records:\n{df.to_json(orient='records')}\n")
print(f"index:\n{df.to_json(orient='index')}\n")
print(f"columns:\n{df.to_json(orient='columns')}\n")
print(f"values:\n{df.to_json(orient='values')}\n")
print(f"table:\n{df.to_json(orient='table')}\n")

```
**■ 出力結果**
```
split:
{"columns":["id","name","age"],"index":[0,1,2],"data":[[1,"Alice",30],[2,"Bob",25],[3,"John",35]]}

records:
[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Bob","age":25},{"id":3,"name":"John","age":35}]

index:
{"0":{"id":1,"name":"Alice","age":30},"1":{"id":2,"name":"Bob","age":25},"2":{"id":3,"name":"John","age":35}}

columns:
{"id":{"0":1,"1":2,"2":3},"name":{"0":"Alice","1":"Bob","2":"John"},"age":{"0":30,"1":25,"2":35}}

values:
[[1,"Alice",30],[2,"Bob",25],[3,"John",35]]

table:
{"schema":{"fields":[{"name":"index","type":"integer"},{"name":"id","type":"integer"},{"name":"name","type":"string"},{"name":"age","type":"integer"}],"primaryKey":["index"],"pandas_version":"1.4.0"},"data":[{"index":0,"id":1,"name":"Alice","age":30},{"index":1,"id":2,"name":"Bob","age":25},{"index":2,"id":3,"name":"John","age":35}]}
```

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_json.html


pandasでは、さまざまな形式のファイルを読み込み、データ分析・集計を行った後、他のフォーマット形式に変換・出力する可能です。

Pandasについては、下記の記事でもまとめられています。ぜひご覧ください。

https://qiita.com/miyakiyo/items/88febbceb3450635f321

https://qiita.com/miyakiyo/items/d1018de39466ae714bd4