forked from takahiro/analyze_lab_placement_survey
first commit
This commit is contained in:
+145
@@ -0,0 +1,145 @@
|
|||||||
|
研究室配属アンケート
|
||||||
|
86 件の回答
|
||||||
|
第一希希望研究室86 件の回答
|
||||||
|
研究室1(バイオプロセス化…研究室2(バイオマテリアル研…研究室3(ソフト材料化学研究室)研究室6(精密有機化学研究室)研究室7(環境調和有機合成研…研究室8(高分子化学研究室)研究室9(セラミックス化学研…研究室10(極限環境材料科学…1/210.5%7%11.6%9.3%15.1%9.3%
|
||||||
|
|
||||||
|
研究室1(バイオプロセス化学研究室) 9
|
||||||
|
研究室2(バイオマテリアル研究室) 6
|
||||||
|
研究室3(ソフト材料化学研究室) 8
|
||||||
|
研究室6(精密有機化学研究室) 3
|
||||||
|
研究室7(環境調和有機合成研究室) 4
|
||||||
|
研究室8(高分子化学研究室) 13
|
||||||
|
研究室9(セラミックス化学研究室) 8
|
||||||
|
研究室10(極限環境材料科学研究室) 7
|
||||||
|
研究室11(計測化学研究室) 0
|
||||||
|
研究室12(触媒化学研究室) 2
|
||||||
|
研究室13(表面電気化学研究室) 1
|
||||||
|
研究室14(資源反応工学研究室) 5
|
||||||
|
研究室15(環境化学研究室) 2
|
||||||
|
研究室16(分子構造解析化学) 1
|
||||||
|
研究室18(分子集合体化学研究室) 7
|
||||||
|
研究室19(環境マネジメント工学研究室) 10
|
||||||
|
研究室1(バイオプロセス化学研究室)
|
||||||
|
9 件の回答
|
||||||
|
3003503703803854004010122 (22.2%)2 (22.2%)1 (11.1%)1 (11.1%)1 (11.1%)1 (11.1%)1 (11.1%)
|
||||||
|
値 カウント
|
||||||
|
300 2
|
||||||
|
350 2
|
||||||
|
370 1
|
||||||
|
380 1
|
||||||
|
385 1
|
||||||
|
400 1
|
||||||
|
401 1
|
||||||
|
研究室2(バイオマテリアル研究室)
|
||||||
|
6 件の回答
|
||||||
|
210
|
||||||
|
333
|
||||||
|
300
|
||||||
|
350
|
||||||
|
223
|
||||||
|
200
|
||||||
|
研究室3(ソフト材料化学研究室)
|
||||||
|
8 件の回答
|
||||||
|
370
|
||||||
|
300
|
||||||
|
353
|
||||||
|
350
|
||||||
|
390
|
||||||
|
320
|
||||||
|
428
|
||||||
|
0
|
||||||
|
研究室6(精密有機化学研究室)
|
||||||
|
3 件の回答
|
||||||
|
356
|
||||||
|
395
|
||||||
|
280
|
||||||
|
研究室7(環境調和有機合成研究室)
|
||||||
|
4 件の回答
|
||||||
|
226
|
||||||
|
210
|
||||||
|
392
|
||||||
|
379
|
||||||
|
研究室8(高分子化学研究室)
|
||||||
|
13 件の回答
|
||||||
|
435
|
||||||
|
243.04
|
||||||
|
428
|
||||||
|
398
|
||||||
|
410
|
||||||
|
360
|
||||||
|
380
|
||||||
|
240
|
||||||
|
432
|
||||||
|
440
|
||||||
|
317
|
||||||
|
315
|
||||||
|
441
|
||||||
|
研究室9(セラミックス化学研究室)
|
||||||
|
8 件の回答
|
||||||
|
360
|
||||||
|
293
|
||||||
|
392
|
||||||
|
220
|
||||||
|
192
|
||||||
|
329
|
||||||
|
391
|
||||||
|
376
|
||||||
|
研究室10(極限環境材料科学研究室)
|
||||||
|
7 件の回答
|
||||||
|
380
|
||||||
|
288
|
||||||
|
300
|
||||||
|
375
|
||||||
|
280
|
||||||
|
310
|
||||||
|
359
|
||||||
|
研究室11(計測化学研究室)
|
||||||
|
0 件の回答
|
||||||
|
この質問にはまだ回答がありません。
|
||||||
|
研究室12(触媒化学研究室)
|
||||||
|
2 件の回答
|
||||||
|
1
|
||||||
|
190.9
|
||||||
|
研究室13(表面電気化学研究室)
|
||||||
|
1 件の回答
|
||||||
|
250
|
||||||
|
研究室14(資源反応工学研究室)
|
||||||
|
5 件の回答
|
||||||
|
230
|
||||||
|
224
|
||||||
|
235
|
||||||
|
300
|
||||||
|
332
|
||||||
|
研究室15(環境化学研究室)
|
||||||
|
2 件の回答
|
||||||
|
258
|
||||||
|
200
|
||||||
|
研究室16(分子構造解析化学)
|
||||||
|
1 件の回答
|
||||||
|
194
|
||||||
|
研究室18(分子集合体化学研究室)
|
||||||
|
7 件の回答
|
||||||
|
3103503553643694030121 (14.3%)2 (28.6%)1 (14.3%)1 (14.3%)1 (14.3%)1 (14.3%)
|
||||||
|
値 カウント
|
||||||
|
310 1
|
||||||
|
350 2
|
||||||
|
355 1
|
||||||
|
364 1
|
||||||
|
369 1
|
||||||
|
403 1
|
||||||
|
研究室19(環境マネジメント工学研究室)
|
||||||
|
10 件の回答
|
||||||
|
19159.282002502603200121 (10%)1 (10%)1 (10%)2 (20%)2 (20%)2 (20%)1 (10%)
|
||||||
|
値 カウント
|
||||||
|
1 1
|
||||||
|
9 1
|
||||||
|
159.28 1
|
||||||
|
200 2
|
||||||
|
250 2
|
||||||
|
260 2
|
||||||
|
320 1
|
||||||
|
このコンテンツは Google が作成または承認したものではありません。 - フォームのオーナーに問い合わせる - 利用規約 - プライバシー ポリシー
|
||||||
|
|
||||||
|
このフォームが不審だと思われる場合 報告
|
||||||
|
Google フォーム
|
||||||
|
.
|
||||||
+185
@@ -0,0 +1,185 @@
|
|||||||
|
#+TITLE: 研究室配属希望調査 解析スクリプト
|
||||||
|
#+AUTHOR: Takahiro Ohkubo
|
||||||
|
#+OPTIONS: toc:2 num:nil
|
||||||
|
|
||||||
|
* 概要
|
||||||
|
|
||||||
|
Google Forms で実施した3年生の研究室配属希望調査について、フォーム結果
|
||||||
|
画面からコピーしたテキストを解析し、研究室ごとの希望者数および GPA の
|
||||||
|
統計量を集計する Python スクリプトです。
|
||||||
|
|
||||||
|
Google Forms の管理者権限がなくCSVファイルを直接取得できない場合でも、
|
||||||
|
結果画面に表示されたテキストをコピーしてファイルに保存することで解析で
|
||||||
|
きます。
|
||||||
|
|
||||||
|
主な処理内容は以下のとおりです。
|
||||||
|
|
||||||
|
- Google Forms の結果テキストから研究室番号を抽出
|
||||||
|
- 各研究室の回答値を抽出
|
||||||
|
- 在校生と編入生を分類
|
||||||
|
- 研究室ごとの希望者数を集計
|
||||||
|
- 在校生 GPA の平均値を計算
|
||||||
|
- 在校生 GPA の標準偏差を計算
|
||||||
|
- 元データを Excel ファイルとして保存
|
||||||
|
|
||||||
|
* 必要な環境
|
||||||
|
|
||||||
|
Python 3 が必要です。
|
||||||
|
使用する Python パッケージは以下です。
|
||||||
|
- numpy
|
||||||
|
- pandas
|
||||||
|
- openpyxl
|
||||||
|
必要に応じて以下のようにインストールします。
|
||||||
|
|
||||||
|
#+begin_src sh
|
||||||
|
condat install numpy pandas openpyxl
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
* 入力データ
|
||||||
|
|
||||||
|
Google Forms のアンケート結果画面に表示されたテキストをコピーし、テキ
|
||||||
|
ストファイルとして保存します。
|
||||||
|
|
||||||
|
例えば、
|
||||||
|
#+begin_example
|
||||||
|
googleform.txt
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
というファイル名で保存します。入力データには、以下のような研究室ごとの
|
||||||
|
回答結果が含まれていることを想定しています。
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
研究室10(極限環境材料科学研究室)
|
||||||
|
7 件の回答
|
||||||
|
380
|
||||||
|
288
|
||||||
|
300
|
||||||
|
375
|
||||||
|
280
|
||||||
|
310
|
||||||
|
359
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
また、Google Forms の表示形式によっては、
|
||||||
|
同じ値が複数回出現する場合に以下のような
|
||||||
|
「値 / カウント」形式になることがあります。
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
値 カウント
|
||||||
|
300 2
|
||||||
|
350 2
|
||||||
|
370 1
|
||||||
|
380 1
|
||||||
|
385 1
|
||||||
|
400 1
|
||||||
|
401 1
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
この場合、カウント数に応じて値を展開します。例えば、
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
300 2
|
||||||
|
350 2
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
は内部的に、
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
300
|
||||||
|
300
|
||||||
|
350
|
||||||
|
350
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
として処理されます。
|
||||||
|
|
||||||
|
* 実行方法
|
||||||
|
#+begin_src sh
|
||||||
|
./analyze_lab_assignment_survey.py googleform.txt
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
* 出力
|
||||||
|
実行すると、研究室ごとの集計結果が標準出力に表示されます。
|
||||||
|
|
||||||
|
出力例:
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
研究室 在校生 編入生 GPA mean GPA std
|
||||||
|
----------------------------------------------------
|
||||||
|
1 9 0 359.6 38.4
|
||||||
|
2 6 0 269.3 66.3
|
||||||
|
3 8 1 358.7 42.7
|
||||||
|
6 3 0 343.7 58.5
|
||||||
|
7 4 0 301.8 97.1
|
||||||
|
8 13 0 372.2 72.3
|
||||||
|
9 8 0 319.1 77.6
|
||||||
|
10 7 0 327.4 42.6
|
||||||
|
12 2 1 190.9 -
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
|
||||||
|
* 在校生と編入生の判定
|
||||||
|
|
||||||
|
このスクリプトでは、回答値について
|
||||||
|
|
||||||
|
#+begin_src python
|
||||||
|
df_["値"] > 10
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
を満たす回答を在校生の GPA データとして扱います。
|
||||||
|
|
||||||
|
したがって、
|
||||||
|
|
||||||
|
- 値 > 10 : 在校生の GPA
|
||||||
|
- 値 <= 10 : 編入生
|
||||||
|
|
||||||
|
として分類します。
|
||||||
|
|
||||||
|
* GPA の統計量
|
||||||
|
|
||||||
|
研究室ごとに、在校生のみを抽出して GPA の平均値と標準偏差を計算します。
|
||||||
|
|
||||||
|
平均値:
|
||||||
|
|
||||||
|
#+begin_src python
|
||||||
|
df__["値"].mean()
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
標準偏差:
|
||||||
|
|
||||||
|
#+begin_src python
|
||||||
|
df__["値"].std()
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
* 注意事項
|
||||||
|
|
||||||
|
- 入力テキストは Google Forms の表示形式に依存します。
|
||||||
|
- Google Forms 側の HTML やコピー形式が変更された場合、正規表現の修正が必要になる可能性があります。
|
||||||
|
- 編入生の判定は「値が10以下」という運用上の仮定に基づいています。
|
||||||
|
- GPA が実際に10以下となる別形式のデータを使用する場合は、判定条件を変更してください。
|
||||||
|
- 回答値の誤入力は自動修正しません。
|
||||||
|
- 研究室番号が存在しても回答数が0件の場合、その研究室は出力から除外されます。
|
||||||
|
|
||||||
|
* ファイル構成例
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
.
|
||||||
|
├── README.org
|
||||||
|
├── analyze_lab_assignment_survey.py
|
||||||
|
└── googleform.txt
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
実行後:
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
.
|
||||||
|
├── README.org
|
||||||
|
├── analyze_lab_assignment_survey.py
|
||||||
|
├── analyze_lab_assignment_survey.xlsx
|
||||||
|
└── googleform.txt
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
* ライセンス
|
||||||
|
|
||||||
|
研究室内または教育業務での利用を想定しています。
|
||||||
|
必要に応じてライセンス条件を追記してください。
|
||||||
Executable
+183
@@ -0,0 +1,183 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
import numpy as np
|
||||||
|
import re
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
|
def parse_google_form_results(text):
|
||||||
|
"""
|
||||||
|
Google Forms のアンケート結果をコピペしたテキストから、
|
||||||
|
|
||||||
|
研究室番号 | 値
|
||||||
|
|
||||||
|
の DataFrame を生成する。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
text : str
|
||||||
|
Google Forms 結果ページからコピーしたテキスト
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
pandas.DataFrame
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 「第一希希望研究室...」の集計部分は除外したいので、
|
||||||
|
# 最初の個別研究室ブロックから開始する
|
||||||
|
m = re.search(
|
||||||
|
r'^研究室\s*[0-90-9]+.*?\n[0-90-9]+\s*件の回答',
|
||||||
|
text,
|
||||||
|
flags=re.MULTILINE
|
||||||
|
)
|
||||||
|
|
||||||
|
if not m:
|
||||||
|
raise ValueError("個別研究室ブロックが見つかりません")
|
||||||
|
|
||||||
|
text = text[m.start():]
|
||||||
|
|
||||||
|
# 各研究室ブロックを取得
|
||||||
|
pattern = re.compile(
|
||||||
|
r'^研究室\s*([0-90-9]+).*?\n'
|
||||||
|
r'([0-90-9]+)\s*件の回答\s*\n'
|
||||||
|
r'(.*?)'
|
||||||
|
r'(?=^研究室\s*[0-90-9]+.*?\n[0-90-9]+\s*件の回答|\Z)',
|
||||||
|
flags=re.MULTILINE | re.DOTALL
|
||||||
|
)
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
|
||||||
|
for match in pattern.finditer(text):
|
||||||
|
lab_no_str = match.group(1)
|
||||||
|
n_answers_str = match.group(2)
|
||||||
|
body = match.group(3).strip()
|
||||||
|
|
||||||
|
# 全角数字対応
|
||||||
|
trans = str.maketrans("0123456789", "0123456789")
|
||||||
|
|
||||||
|
lab_no = int(lab_no_str.translate(trans))
|
||||||
|
n_answers = int(n_answers_str.translate(trans))
|
||||||
|
|
||||||
|
# 回答数 0 はスキップ
|
||||||
|
if n_answers == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# Case 1:
|
||||||
|
# 「値 カウント」表がある場合
|
||||||
|
#
|
||||||
|
# 例:
|
||||||
|
# 値 カウント
|
||||||
|
# 300 2
|
||||||
|
# 350 2
|
||||||
|
# --------------------------------------------------
|
||||||
|
if re.search(r'^値\s+カウント\s*$', body, flags=re.MULTILINE):
|
||||||
|
table_part = re.split(
|
||||||
|
r'^値\s+カウント\s*$',
|
||||||
|
body,
|
||||||
|
maxsplit=1,
|
||||||
|
flags=re.MULTILINE
|
||||||
|
)[1]
|
||||||
|
|
||||||
|
count_rows = re.findall(
|
||||||
|
r'^\s*(-?\d+(?:\.\d+)?)\s+(\d+)\s*$',
|
||||||
|
table_part,
|
||||||
|
flags=re.MULTILINE
|
||||||
|
)
|
||||||
|
|
||||||
|
values = []
|
||||||
|
|
||||||
|
for value_str, count_str in count_rows:
|
||||||
|
value = float(value_str)
|
||||||
|
count = int(count_str)
|
||||||
|
|
||||||
|
values.extend([value] * count)
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# Case 2:
|
||||||
|
# 回答値が1行ずつ並んでいる場合
|
||||||
|
#
|
||||||
|
# 例:
|
||||||
|
# 210
|
||||||
|
# 333
|
||||||
|
# 300
|
||||||
|
# --------------------------------------------------
|
||||||
|
else:
|
||||||
|
values = []
|
||||||
|
|
||||||
|
for line in body.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
if re.fullmatch(r'-?\d+(?:\.\d+)?', line):
|
||||||
|
values.append(float(line))
|
||||||
|
|
||||||
|
# 回答数との整合性チェック
|
||||||
|
if len(values) != n_answers:
|
||||||
|
print(
|
||||||
|
f"警告: 研究室{lab_no}: "
|
||||||
|
f"回答数={n_answers}, 抽出数={len(values)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
for value in values:
|
||||||
|
rows.append({
|
||||||
|
"研究室番号": lab_no,
|
||||||
|
"値": value
|
||||||
|
})
|
||||||
|
|
||||||
|
df = pd.DataFrame(rows)
|
||||||
|
|
||||||
|
# 整数値だけなら見やすくする
|
||||||
|
if not df.empty:
|
||||||
|
if (df["値"] % 1 == 0).all():
|
||||||
|
df["値"] = df["値"].astype(int)
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
par = argparse.ArgumentParser(description="test")
|
||||||
|
par.add_argument('googleform_str')
|
||||||
|
args = par.parse_args()
|
||||||
|
|
||||||
|
# コピペしたテキストをファイルに保存しておく
|
||||||
|
with open(args.googleform_str, encoding="utf-8") as f:
|
||||||
|
text = f.read()
|
||||||
|
|
||||||
|
df = parse_google_form_results(text)
|
||||||
|
df_r = pd.DataFrame()
|
||||||
|
for n in np.unique(df["研究室番号"]):
|
||||||
|
df_ = df.loc[df["研究室番号"] == n, :]
|
||||||
|
df__ = df_.loc[df_["値"] > 10, :]
|
||||||
|
ave, std = df__.mean(), df__.std()
|
||||||
|
df_r.loc[n, "在校生"] = df_.shape[0]
|
||||||
|
df_r.loc[n, "編入生"] = df_.shape[0] - df__.shape[0]
|
||||||
|
df_r.loc[n, "GPA mean"] = df__["値"].mean()
|
||||||
|
df_r.loc[n, "GPA std"] = df__["値"].std()
|
||||||
|
df_r[["在校生", "編入生"]] = df_r[["在校生", "編入生"]].astype(int)
|
||||||
|
# 表示用
|
||||||
|
df_show = df_r.copy()
|
||||||
|
df_show.index.name = "研究室"
|
||||||
|
df_show["GPA mean"] = df_show["GPA mean"].round(1)
|
||||||
|
df_show["GPA std"] = df_show["GPA std"].round(1)
|
||||||
|
print(
|
||||||
|
f"{'研究室':>6} "
|
||||||
|
f"{'在校生':>6} "
|
||||||
|
f"{'編入生':>6} "
|
||||||
|
f"{'GPA mean':>10} "
|
||||||
|
f"{'GPA std':>10}"
|
||||||
|
)
|
||||||
|
print("-" * 52)
|
||||||
|
for n, row in df_r.iterrows():
|
||||||
|
mean = f"{row['GPA mean']:.1f}"
|
||||||
|
std = "-" if pd.isna(row["GPA std"]) else f"{row['GPA std']:.1f}"
|
||||||
|
print(
|
||||||
|
f"{n:>6} "
|
||||||
|
f"{int(row['在校生']):>8} "
|
||||||
|
f"{int(row['編入生']):>8} "
|
||||||
|
f"{mean:>10} "
|
||||||
|
f"{std:>10}")
|
||||||
|
|
||||||
|
# outfile = __file__.replace(".py", ".xlsx")
|
||||||
|
# df.to_excel(outfile)
|
||||||
|
# print(f"{outfile} was created.")
|
||||||
Reference in New Issue
Block a user