【言語学者向け】エクセルの調査票からpdfの調査票を作成する (Python + TeX)

# 想定読者
フィールド調査をする言語学者

# 目的
エクセルに記入した調査票から、調査時に文字を書き入れるためのpdfの調査票を作る。

# できること
こんな感じのエクセルから
![スクリーンショット 2022-10-29 21.32.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573180/62089396-21dd-62d6-8155-665cc05da97e.png)

#こんな感じのpdfを生成する
![スクリーンショット 2022-10-29 22.11.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573180/13fa765a-e7c5-7c21-8418-b057cd113e37.png)

# 背景
多くの研究者は、フィールドワークの準備としてエクセルで調査票を管理することが多いと思う。また、共同プロジェクトで共通の調査票を用いる場合、エクセルで調査票が配布されることがほとんどである。

調査時にPCでエクセルにそのまま書き込むスタイルを取る人であれば問題ないが、
- 目の前でパソコンをカタカタされるのを好まない被調査者がいる
- 音声記号や超分節要素が打ちにくい
- マイクに打鍵音が入る
- 追加の情報を入れづらい
などの問題があるので、私は紙のノートやタブレットに調査メモを書き込むようにしている。

しかし、調査票のエクセルファイルをそのまま印刷してしまうと、
- 余白が少なすぎる
- ページレイアウトが綺麗でない

などの問題がある。

そこで、
- 元のエクセルファイルのデータ構造は変更したくない
- 十分な余白をとって適切にレイアウトした書き込み用の調査票がほしい

という需要が発生する。

これを解決するために、エクセル形式の調査票からpdf形式でレイアウトされた調査票を出力する方法を紹介する。



# 環境
- Python 3系
    * pandas
- TeX (今回はpLaTeX)


# 手順
おおまかな手順を紹介する。

1. Pythonのpandasモジュールを使ってエクセルファイルを読み込む
1. 表を一行ずつ処理し、.texファイルを生成する
1. あらかじめ.texのメインファイルを作っておき、先程生成した.texファイルをインプットする
1. TeXで組版する

# 手順1: 必要なファイルを用意する

## ファイル構成
```
root
├── make_questionnaire.py
├── questionnaire.xlsx
└── tex
    ├── questionnaire.tex
    └── questionnaire_main.tex
```
フォルダの中に`questionnaire.xlsx`(調査票の元エクセルファイル)、`./tex/questionnaire.tex`(処理するTeXファイル)、`make_questionnaire.py`(Pythonスクリプト)を用意する。

## ファイルの用意

### questionnaire.xlsx

![スクリーンショット 2022-10-29 21.32.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573180/62089396-21dd-62d6-8155-665cc05da97e.png)

こんな感じの内容にする。今回は「ID」列と「例文」列しか使わないが、他の列があってもいい。行は何行でもOK。`questionnaire.py`（後述）で列名を指定して処理しているので、「ID」列と「例文」列の列名は変えないこと（列名を変えるなら`questionnaire.py`の処理を適宜変えること）。

### questionnaire_main.tex
`questionnaire_main.tex`は組版処理を行うためのメインファイル。ここにプリアンブル、`\input`命令、document環境などを書き込んでいく。
最小構成は以下のような感じ。
プリアンブルで、各例文を整形するための`\entry`というマクロを定義しておく。

```tex: questionnaire_main.tex
\documentclass{article}
\newcommand{\entry}[2]{\noindent #1: #2 \vspace{20zw}}

\begin{document}
\input{questionnaire.tex}
\end{document}
```

# 手順2: Pythonの処理
ここからPythonの処理を書いていく。
手順としては、

### エクセルファイル読み込み
```Python3: make_questionnaire.py
import pandas as pd
df = pd.read_excel("questionnaire.xlsx")
```
pandasモジュールをpdという名前でインポートする。
pandasの`read_excel()`という関数でエクセルファイルを読み込み、dfという変数に格納する。

### 一行ずつ処理して文字列型のオブジェクトにする

```Python3: make_questionnaire.py
tex_text = ""
for index, row in df.iterrows():
    tex_text += f"\\entry{{{row['ID']}}}{{{row['例文']}}}\n\n"
```
`pandas.DataFrame`の`iterrows()`メソッドを使うと、`(行名, その行のデータ(Series))`を一行ずつ取り出せる。これをforで一行ずつ処理していく。ここではこれらを`index`, `row` という名前にしているので、`row['列名']`とすると、その行の'列名'列のデータが取り出せる。

まず`tex_text`という空の文字列を用意し、そこに一行ずつTeXで予め定義した`\entry`命令を書き込んでいく。つまり、調査票の各行について
```tex
\entry{ID}{例文}
```
という命令を書き込む。
`f"\\entry{{{row['ID']}}}{{{row['例文']}}}\n\n"`はフォーマット済みリテラルなので、{}で囲まれた部分は文字列として扱われず、オブジェクトを直接書き込める。
要するに、

```Python3
tex_text = tex_text + "\\entry{" + row['ID'] + "}{" + row['例文'] + "}" 
```
とするのと同じ。
これで`tex_text`という変数に全ての行の`\entry`命令が入ったことになる。

### .texファイルとして書き込み

```Python3: make_questionnaire.py
with open("./tex/questionnaire.tex", mode="w") as f:
    f.write(tex_text)
```
`with`構文の中で書き込みモードで`open()`関数を使い、`./tex/questionnaire.tex`というファイルに`tex_text`の中身を書き込む。
これで.texファイルの生成は終了

## TeXの組版
これで必要なファイルは全部そろったので、TeXを組版する。TeXエディタを起動して組版してもいいし、VSCodeとかを使っている人ならそこから組版してもいいが、せっかくなのでPythonからシェルを叩いてTeXを組版する。

```Python3: make_questionnaire.py
import glob
import os
import subprocess

def delete_ext():
    ext_list = ["aux", "dvi", "log"]
    for ext in ext_list:
        file_to_delete = glob.glob(f"*.{ext}")
        for f in file_to_delete:
            os.remove(f)

os.chdir("tex")
delete_ext()
subprocess.call(["platex questionnaire_main.tex"],shell=True)
delete_ext()
```

`delete_ext()`で余計な中間ファイルを削除する関数を定義している。
その後は、`./tex`に移動し、中間ファイルを削除し、`subprocess.call()`でシェルを実行してTeXを組版している。

### アウトプット
![スクリーンショット 2022-10-29 22.11.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573180/13fa765a-e7c5-7c21-8418-b057cd113e37.png)
こんな感じのpdfが生成される。間隔を調整したい場合は`\entry`の定義の`\vspace`を適宜調整する。

# 最終的なコード
```Python3: make_questionnaire.py
import glob
import os
import subprocess
import pandas as pd

def delete_ext():
    ext_list = ["aux", "dvi", "log"]
    for ext in ext_list:
        file_to_delete = glob.glob(f"*.{ext}")
        for f in file_to_delete:
            os.remove(f)

df = pd.read_excel("questionnaire.xlsx")
tex_text = ""

for index, row in df.iterrows():
    tex_text += f"\\entry{{{row['ID']}}}{{{row['例文']}}}\n\n"

with open("./tex/questionnaire.tex", mode="w") as f:
    f.write(tex_text)

os.chdir("tex")
delete_ext()
subprocess.call(["platex questionnaire_main.tex"],shell=True)
delete_ext()
```

```tex:questionnaire_main.tex
\documentclass{article}
\newcommand{\entry}[2]{\noindent #1: #2 \vspace{20zw}}

\begin{document}
\input{questionnaire.tex}
\end{document}
```