PythonによるExcelファイル比較スクリプト

## 概要
　このスクリプトは、Bashスクリプトの中でPythonを実行し、2つのExcelファイルを比較して差分を出力する。具体的には、以下のことを行う:
・第一列（キー列）を基準に、片方のファイルにしかない行をリストアップする。
・キー列が一致する行について、各列の差分をチェックし、違いがある場合にそれを出力する。

## スクリプト
~~~python

#!/bin/bash

if [ "$#" -ne 2 ]; then
    echo "使用方法: compare_excel <file1.xlsx> <file2.xlsx>"
    exit 1
fi

FILE1="$1"
FILE2="$2"

if [ ! -f "$FILE1" ]; then
    echo "エラー: ファイル $FILE1 が見つかりません。"
    exit 1
fi

if [ ! -f "$FILE2" ]; then
    echo "エラー: ファイル $FILE2 が見つかりません。"
    exit 1
fi


python3 <<EOF
import pandas as pd

def compare_excel(file1, file2):
    try:
        print(f"Comparing files: {file1} and {file2}")

        # Excelファイルを読み込む
        df1 = pd.read_excel(file1, dtype=str)
        df2 = pd.read_excel(file2, dtype=str)

        print("DF1:", df1.head(), sep="\n")
        print("DF1:", df1.tail(), sep="\n")
        print("DF2:", df2.head(), sep="\n")
        print("DF2:", df2.tail(), sep="\n")

        # 1列目（キー列）を動的に検出
        key_column = df1.columns[0]
        print(f"キー列: {key_column}")

        # 列ラベルの一致を確認し、共通列のみを使用
        common_columns = df1.columns.intersection(df2.columns)
        if not common_columns.any():
            print("共通する列がありません。比較できません。")
            return

        df1 = df1[common_columns]
        df2 = df2[common_columns]

        # デバッグ: インデックスの確認
        print("インデックス確認: df1インデックス:", df1.index)
        print("インデックス確認: df2インデックス:", df2.index)

        # デバッグ: 比較前のデータを表示
        print(f"比較開始: df1のサイズ: {df1.shape}, df2のサイズ: {df2.shape}")

        # 追加された行
        added_rows = df2[~df2[key_column].isin(df1[key_column])]
        print("\n[file2にのみ存在]")
        if not added_rows.empty:
            print(added_rows)

        # 削除された行
        removed_rows = df1[~df1[key_column].isin(df2[key_column])]
        print("\n[file1にのみ存在)]")
        if not removed_rows.empty:
            print(removed_rows)

        print("\n")

        # セルごとの比較（キー列が一致する行のみ）
        for col in common_columns:
            if col == key_column:
                continue  # キー列は比較しない
            merged = pd.merge(df1[[key_column, col]], df2[[key_column, col]], on=key_column, suffixes=('_file1', '_file2'))
            diff = merged[(merged[f"{col}_file1"] != merged[f"{col}_file2"]) &
                          ~(merged[f"{col}_file1"].isna() & merged[f"{col}_file2"].isna())]
            for _, row in diff.iterrows():
                print(f"行 '{row[key_column]}', 列 '{col}' の違い: '{row[f'{col}_file1']}' (file1) vs '{row[f'{col}_file2']}' (file2)")

    except Exception as e:
        print(f"エラーが発生しました: {e}")

if __name__ == "__main__":
    # Bashの変数をPythonに渡す
    file1 = "${FILE1}"
    file2 = "${FILE2}"

    compare_excel(file1, file2)
EOF
~~~

## 解説

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

~~~Python
        # Excelファイルを読み込む
        df1 = pd.read_excel(file1, dtype=str)
~~~

　pandasのread_execelを用いてファイルを読み込む。dtype=strは全てのデータを文字列として読み込むオプション。

### キー列検出

~~~Python
        # 1列目（キー列）を動的に検出
        key_column = df1.columns[0]
~~~

 1列目をキー列として指定する。

### 共通列検出

~~~Python
        # 列ラベルの一致を確認し、共通列のみを使用
        common_columns = df1.columns.intersection(df2.columns)
        if not common_columns.any():
            print("共通する列がありません。比較できません。")
            return
~~~

　列ラベル同士を比較して両方のデータフレームに共通する列を見つける。共通列がなければ比較できない旨を表示して終了する。

### 行差分の検出

~~~Python
        added_rows = df2[~df2[key_column].isin(df1[key_column])]
~~~

・df2[key_column]: df2データフレームの key_column 列（キー列）の値を取得。
・.isin(df1[key_column]): df2のキー列の各値が df1のキー列に含まれているかどうかをチェック。ブール型を返し、Trueは df1に存在する値、Falseは存在しない値に対応。
・~ (チルダ): 論理否定演算子で、ブール値を反転させる。この場合、df1に存在しない値がTrueとなる。
・df2[...]: ここで、df2の各行について、~df2[key_column].isin(df1[key_column]) の条件を満たす行（Trueになる行）だけを選択して新しいデータフレームを作成する。

つまり、このコードの意味は:
「df2 に含まれるが df1 に含まれていないキー列の行」を added_rows に格納する ということ。これにより、file2 にだけ存在する行（追加された行）を抽出している。

### セルの比較

~~~Python
        # セルごとの比較（キー列が一致する行のみ）
        for col in common_columns:
            if col == key_column:
                continue  # キー列は比較しない
            merged = pd.merge(df1[[key_column, col]], df2[[key_column, col]], on=key_column, suffixes=('_file1', '_file2'))
            diff = merged[(merged[f"{col}_file1"] != merged[f"{col}_file2"]) &
                          ~(merged[f"{col}_file1"].isna() & merged[f"{col}_file2"].isna())]
            for _, row in diff.iterrows():
                print(f"行 '{row[key_column]}', 列 '{col}' の違い: '{row[f'{col}_file1']}' (file1) vs '{row[f'{col}_file2']}' (file2)")
~~~

　両方のデータフレームに共通する列について比較する。キー列は比較対象外なのでスキップする。

merged = pd.merge(df1[[key_column, col]], df2[[key_column, col]], on=key_column, suffixes=('_file1', '_file2'))
・pd.merge は df1 と df2 の対応する列（key_columnとcol）を基にマージする。
・on=key_column はキー列をマージの基準にする。
・suffixes=('_file1', '_file2') は同じ名前の列に _file1 や _file2 というサフィックスを付け分けることで、どちらのファイルのデータかを区別する。

diff = merged[(merged[f"{col}_file1"] != merged[f"{col}_file2"]) &
 ~(merged[f"{col}_file1"].isna() & merged[f"{col}_file2"].isna())]
・file1 と file2 の対応する列（例えばcol）の値が異なる行を抽出。
・!= は不一致のチェック、isna() は NaN（欠損値）のチェック。~ は論理否定。
・&(アンパサンド) は論理積で、両方の条件が成り立つ行を選ぶ。特に、両方がNaNの場合も除外。

・diff.iterrows() で diff データフレームの各行を反復する。_,は本来はindexが入るのだが、使用しないので省略したという書き方
・print 文で、キー列の値と、その列で差異が生じた具体的な値を表示する。file1とfile2の値を比較して、どこに差異があるのかを明確に伝える。