想定読者
フィールド調査をする言語学者
目的
エクセルに記入した調査票から、調査時に文字を書き入れるためのpdfの調査票を作る。
できること
背景
多くの研究者は、フィールドワークの準備としてエクセルで調査票を管理することが多いと思う。また、共同プロジェクトで共通の調査票を用いる場合、エクセルで調査票が配布されることがほとんどである。
調査時にPCでエクセルにそのまま書き込むスタイルを取る人であれば問題ないが、
- 目の前でパソコンをカタカタされるのを好まない被調査者がいる
- 音声記号や超分節要素が打ちにくい
- マイクに打鍵音が入る
- 追加の情報を入れづらい
などの問題があるので、私は紙のノートやタブレットに調査メモを書き込むようにしている。
しかし、調査票のエクセルファイルをそのまま印刷してしまうと、
- 余白が少なすぎる
- ページレイアウトが綺麗でない
などの問題がある。
そこで、
- 元のエクセルファイルのデータ構造は変更したくない
- 十分な余白をとって適切にレイアウトした書き込み用の調査票がほしい
という需要が発生する。
これを解決するために、エクセル形式の調査票からpdf形式でレイアウトされた調査票を出力する方法を紹介する。
環境
- Python 3系
- pandas
- TeX (今回はpLaTeX)
手順
おおまかな手順を紹介する。
- Pythonのpandasモジュールを使ってエクセルファイルを読み込む
- 表を一行ずつ処理し、.texファイルを生成する
- あらかじめ.texのメインファイルを作っておき、先程生成した.texファイルをインプットする
- 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
こんな感じの内容にする。今回は「ID」列と「例文」列しか使わないが、他の列があってもいい。行は何行でもOK。questionnaire.py
(後述)で列名を指定して処理しているので、「ID」列と「例文」列の列名は変えないこと(列名を変えるならquestionnaire.py
の処理を適宜変えること)。
questionnaire_main.tex
questionnaire_main.tex
は組版処理を行うためのメインファイル。ここにプリアンブル、\input
命令、document環境などを書き込んでいく。
最小構成は以下のような感じ。
プリアンブルで、各例文を整形するための\entry
というマクロを定義しておく。
\documentclass{article}
\newcommand{\entry}[2]{\noindent #1: #2 \vspace{20zw}}
\begin{document}
\input{questionnaire.tex}
\end{document}
手順2: Pythonの処理
ここからPythonの処理を書いていく。
手順としては、
エクセルファイル読み込み
import pandas as pd
df = pd.read_excel("questionnaire.xlsx")
pandasモジュールをpdという名前でインポートする。
pandasのread_excel()
という関数でエクセルファイルを読み込み、dfという変数に格納する。
一行ずつ処理して文字列型のオブジェクトにする
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
命令を書き込んでいく。つまり、調査票の各行について
\entry{ID}{例文}
という命令を書き込む。
f"\\entry{{{row['ID']}}}{{{row['例文']}}}\n\n"
はフォーマット済みリテラルなので、{}で囲まれた部分は文字列として扱われず、オブジェクトを直接書き込める。
要するに、
tex_text = tex_text + "\\entry{" + row['ID'] + "}{" + row['例文'] + "}"
とするのと同じ。
これでtex_text
という変数に全ての行の\entry
命令が入ったことになる。
.texファイルとして書き込み
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を組版する。
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を組版している。
アウトプット
こんな感じのpdfが生成される。間隔を調整したい場合は\entry
の定義の\vspace
を適宜調整する。
最終的なコード
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()
\documentclass{article}
\newcommand{\entry}[2]{\noindent #1: #2 \vspace{20zw}}
\begin{document}
\input{questionnaire.tex}
\end{document}