[Python]アマチュア無線のログをCSVやADIFに変換してみた（1）ExcelブックからCSVへ

## はじめに
&emsp;こんにちは、Hagianです。趣味として、アマチュア無線を楽しんでいます。アマチュア無線では、<ins>いつ・誰と・どんなところから・何を使って交信したか記録（ログ）を取ることがほぼ必須</ins>となっています。パソコンが普及した現在、紙ではなくパソコンでログを取っている人が多く、筆者もその1人です。
&emsp;しかし、筆者のパソコンはMac。さまざまな種類のソフトが有志によって開発されているWindowsと異なり、Mac用のロギングソフトはあまり多くありません ~~（というかほぼ知らない）~~ 。筆者は手軽な表計算ソフトであるExcelを使って、ログを取っているのですが、コンテスト等で書き出す必要がある際、主要ソフトによって出力されたものとフォーマットが違うためにかなり修正しなければなりませんでした。

そこで今回、Pythonを使ってExcel形式のログをCSVやADIFに変換するスクリプトを書いたので記事にします。あまり先例を見かけなかったので、備忘録を兼ねています。

## Excelログのフォーマット

&emsp;ログの書き方・フォーマットは人それぞれだと思いますが、筆者は以下のように書いています（以下のログは架空の交信です）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1021972/44aa3ca9-7c9b-6700-7d6b-b500eddee598.png)


&emsp;これらのログを年ごとにシートに分けて記録しています。Excelから「名前を付けて保存」では、1シートずつしかCSVにできず、少し手間がかかります。そこで、1シートずつCSVとして出力してから、必要部分を抽出することにしました。

## CSVのフォーマット

&emsp;これも色々ありそうなんですが、筆者は以下のよう出力させています。

```log.csv
id,DATE,TIME(JST),frequency,mode,callsign,sent_rst,received_rst,received_qth,received_qra,is_qsl_sent,card_remarks,remarks_1,sent_qth,remarks_2,
1,2023-01-01,12:34,430MHz,FM,JA1RL,59,51,東京都豊島区,鈴木さん,false,Bureau,QSOパーティ,東京都足立区,,
2,2023-04-05,20:16,144MHz,FM,JA7YAB/7,59,59+,山形県天童市,佐藤さん,True,Direct,山形県山形市,偶然,
```

## 実装例

### Excelブックを1シートずつCSVにエクスポート

```python:xlsx2csv.py
import datetime
import pandas as pd
import pathlib

# 現在日時取得
dt_now = datetime.datetime.now().strftime("%Y%m%d%H%M")

# Excelファイル指定
path = "../LogBook.xlsx"
input_file = pd.ExcelFile(path)

# シート名取得
sheet_names = input_file.sheet_names
# ディレクトリがなかったら中間ディレクトリを含めて一括で新規作成、すでに存在していてもエラーは出ない
pathlib.Path(f"../convert_log/tmp/converted_{dt_now[:-4]}/").mkdir(exist_ok=True, parents=True)
    for i in range(len(sheet_names)-2):
        # Excelファイル読み込み
        data = pd.read_excel(path, sheet_names[i], index_col=None)
        # CSV形式で出力
        data.to_csv(f"../convert_log/tmp/converted_{dt_now[:-4]}/{sheet_names[i]}.csv", encoding="utf-8", index=False)

```

### 変換したCSVの必要部分を抽出、別ファイルに保存

```python:csv_extract.py
import csv
import datetime
import pathlib
import pandas as pd


# 現在日時取得
dt_now = datetime.datetime.now().strftime("%Y%m%d%H%M")

# オリジナルのCSVログ読み込み
log_org = pd.read_csv(f"../convert_log/tmp/converted_{dt_now[:-4]}/{sheet_names[i]}.csv",index_col=0)
sheet_name_pos = re.search("converted_",file).end()
sheet_name = file[sheet_name_pos+9:-4]

# 必要な列だけ抽出
log_p = log_org.iloc[:,0:3]
log_q = log_org.iloc[:,4:12]
log_r = log_org.iloc[:,14]
log_s = log_org.iloc[:,16:18]

# 抽出したDataFrameを横方向に連結
log_concat = pd.concat([log_p,log_q,log_r,log_s], axis=1)
# headerをAirHamlog形式に変更
log_concat_header = "id,DATE(JST),TIME,frequency,mode,callsign,sent_rst,received_rst,received_qth,received_qra,is_qsl_sent,card_remarks,remarks_1,sent_qth,remarks_2\n"

# ディレクトリがなかったら新規作成、すでに存在していてもエラーは出ない
pathlib.Path("../convert_log/prep/").mkdir(exist_ok=True, parents=True)
prefile = f"../convert_log/prep/{time_}_{sheet_name}_airhamlog_pre.csv"

with open(prefile, mode="w", encoding="utf-8") as output:
    # 変更したheaderを書き込み
    output.write(log_concat_header)
    # 元のheaderは書き込まない
    log_concat.to_csv(output,header=False)


```

## 完成

これらをまとひとまとめにしたコードがこちら。

```python:log_convert.py
# LogBook.xlsxをCSVにエクスポートし、AirHamlog/POTA等で活用できる形に整えるプログラム

import datetime
import glob
import pathlib
import pandas as pd
import re


def xlsx2csv(time_):
    # Excelファイル指定
    path = "../LogBook.xlsx"
    input_file = pd.ExcelFile(path)

    # シート名取得
    sheet_names = input_file.sheet_names

    pathlib.Path(f"../convert_log/tmp/converted_{time_[:-4]}/").mkdir(exist_ok=True, parents=True)
    for i in range(len(sheet_names)-2):
        # Excelファイル読み込み
        data = pd.read_excel(path, sheet_names[i], index_col=None)
        # CSV形式で出力
        data.to_csv(f"../convert_log/tmp/converted_{time_[:-4]}/{sheet_names[i]}.csv", encoding="utf-8", index=False)


def csv_log_converter(file,time_):
    # オリジナルのCSVログ読み込み
    log_org = pd.read_csv(file,index_col=0)
    sheet_name_pos = re.search("converted_",file).end()
    sheet_name = file[sheet_name_pos+9:-4]

    # 必要な列だけ抽出
    log_p = log_org.iloc[:,0:3]
    log_q = log_org.iloc[:,4:12]
    log_r = log_org.iloc[:,14]
    log_s = log_org.iloc[:,16:18]

    # 抽出したDataFrameを横方向に連結
    log_concat = pd.concat([log_p,log_q,log_r,log_s], axis=1)
    # headerをAirHamlog形式に変更
    log_concat_header = "id,DATE(JST),TIME,frequency,mode,callsign,sent_rst,received_rst,received_qth,received_qra,is_qsl_sent,card_remarks,remarks_1,sent_qth,remarks_2\n"

    # ディレクトリがなかったら新規作成、すでに存在していてもエラーは出ない
    pathlib.Path("../convert_log/prep/").mkdir(exist_ok=True, parents=True)
    prefile = f"../convert_log/prep/{time_}_{sheet_name}_airhamlog_pre.csv"

    with open(prefile, mode="w", encoding="utf-8") as output:
        # 変更したheaderを書き込み
        output.write(log_concat_header)
        # 元のheaderは書き込まない
        log_concat.to_csv(output,header=False)


if __name__ == '__main__':
    # 現在日時取得
    dt_now = datetime.datetime.now().strftime("%Y%m%d%H%M")
    xlsx2csv(dt_now)
    # フォルダ内CSVリスト取得
    csv_list = glob.glob(f"../convert_log/tmp/converted_{dt_now[:-4]}/*.csv")
    # フォルダ内のCSVすべてに対しログ変換を実行
    for i in range(len(csv_list)):
        csv_log_converter(csv_list[i],dt_now)

```

## おわりに

&emsp;次回はAirHamlog用CSVへの変換のしかたについて執筆します。