supports the output format for FY2026.
This commit is contained in:
+170
-48
@@ -4,49 +4,48 @@
|
|||||||
|
|
||||||
* 概要
|
* 概要
|
||||||
|
|
||||||
Google Forms で実施した3年生の研究室配属希望調査について、フォーム結果
|
Google Forms で実施した3年生の研究室配属希望調査について、結果画面から
|
||||||
画面からコピーしたテキストを解析し、研究室ごとの希望者数および GPA の
|
コピーしたテキストを解析し、研究室ごとの希望者数と GPA の統計量を集計する
|
||||||
統計量を集計する Python スクリプトです。
|
Python スクリプトです。
|
||||||
|
|
||||||
Google Forms の管理者権限がなくCSVファイルを直接取得できない場合でも、
|
Google Forms の管理者権限がなく CSV を直接取得できない場合でも、結果画面の
|
||||||
結果画面に表示されたテキストをコピーしてファイルに保存することで解析で
|
テキストをコピーしてファイルに保存すれば解析できます。
|
||||||
きます。
|
|
||||||
|
|
||||||
主な処理内容は以下のとおりです。
|
主な処理内容:
|
||||||
|
|
||||||
- Google Forms の結果テキストから研究室番号を抽出
|
- Google Forms の結果テキストから研究室番号を抽出
|
||||||
- 各研究室の回答値を抽出
|
- 各研究室の回答値を抽出
|
||||||
- 在校生と編入生を分類
|
- 在校生と編入生を分類
|
||||||
- 研究室ごとの希望者数を集計
|
- 研究室ごとの希望者数を集計
|
||||||
- 在校生 GPA の平均値を計算
|
- 在校生 GPA の平均値と標準偏差を計算
|
||||||
- 在校生 GPA の標準偏差を計算
|
- 必要に応じて元データと集計結果を Excel に保存
|
||||||
- 元データを Excel ファイルとして保存
|
|
||||||
|
|
||||||
* 必要な環境
|
* 必要な環境
|
||||||
|
|
||||||
Python 3 が必要です。
|
Python 3 と以下のパッケージが必要です。
|
||||||
使用する Python パッケージは以下です。
|
|
||||||
- numpy
|
- numpy
|
||||||
- pandas
|
- pandas
|
||||||
- openpyxl
|
- openpyxl
|
||||||
必要に応じて以下のようにインストールします。
|
|
||||||
|
Conda を使う場合:
|
||||||
|
|
||||||
#+begin_src sh
|
#+begin_src sh
|
||||||
condat install numpy pandas openpyxl
|
conda install numpy pandas openpyxl
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
* 入力データ
|
* 入力データ
|
||||||
|
|
||||||
Google Forms のアンケート結果画面に表示されたテキストをコピーし、テキ
|
Google Forms の結果画面に表示されたテキストをコピーし、テキストファイル
|
||||||
ストファイルとして保存します。
|
として保存します。
|
||||||
|
|
||||||
|
例:
|
||||||
|
|
||||||
例えば、
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
googleform.txt
|
googleform.txt
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
というファイル名で保存します。入力データには、以下のような研究室ごとの
|
研究室ごとの回答は、例えば以下の形式を想定しています。
|
||||||
回答結果が含まれていることを想定しています。
|
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
研究室10(極限環境材料科学研究室)
|
研究室10(極限環境材料科学研究室)
|
||||||
@@ -60,8 +59,78 @@ googleform.txt
|
|||||||
359
|
359
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
また、Google Forms の表示形式によっては、
|
Google Forms の表示によっては、回答数の前に =GP= が付く場合にも対応します。
|
||||||
同じ値が複数回出現する場合に以下のような
|
|
||||||
|
#+begin_example
|
||||||
|
研究室10
|
||||||
|
GP7 件の回答
|
||||||
|
236
|
||||||
|
377.5
|
||||||
|
292
|
||||||
|
1.8
|
||||||
|
385
|
||||||
|
350
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
* 回答値の形式
|
||||||
|
|
||||||
|
** 1行1回答の形式
|
||||||
|
|
||||||
|
通常は各回答値が1行ずつ並びます。
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
研究室1
|
||||||
|
GP7 件の回答
|
||||||
|
260
|
||||||
|
181
|
||||||
|
395
|
||||||
|
340
|
||||||
|
396
|
||||||
|
380
|
||||||
|
365
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
小数値にも対応します。
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
348.3
|
||||||
|
377.5
|
||||||
|
403.51
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
値の後ろに注記がある場合も、行頭の数値を読み取ります。
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
365(修正済)
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
この場合は =365= として処理します。
|
||||||
|
|
||||||
|
** 編入生
|
||||||
|
|
||||||
|
回答が =編入生= の場合は、内部的に 0 として扱います。
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
研究室2
|
||||||
|
GP6 件の回答
|
||||||
|
編入生
|
||||||
|
217
|
||||||
|
248
|
||||||
|
350
|
||||||
|
342
|
||||||
|
360
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
この例では、
|
||||||
|
|
||||||
|
- 在校生: 5人
|
||||||
|
- 編入生: 1人
|
||||||
|
|
||||||
|
として集計します。
|
||||||
|
|
||||||
|
** 値 / カウント形式
|
||||||
|
|
||||||
|
同じ値が複数回出現する場合、Google Forms の表示によっては
|
||||||
「値 / カウント」形式になることがあります。
|
「値 / カウント」形式になることがあります。
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
@@ -70,12 +139,11 @@ googleform.txt
|
|||||||
350 2
|
350 2
|
||||||
370 1
|
370 1
|
||||||
380 1
|
380 1
|
||||||
385 1
|
|
||||||
400 1
|
|
||||||
401 1
|
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
この場合、カウント数に応じて値を展開します。例えば、
|
この場合、カウント数に応じて値を展開します。
|
||||||
|
|
||||||
|
例えば、
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
300 2
|
300 2
|
||||||
@@ -93,69 +161,123 @@ googleform.txt
|
|||||||
|
|
||||||
として処理されます。
|
として処理されます。
|
||||||
|
|
||||||
|
Google Forms のグラフ表示からコピーした際に、表の前に連結した数字列が
|
||||||
|
含まれることがあります。
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
19423023625427427734435036037001231 (8.3%)...
|
||||||
|
値 カウント
|
||||||
|
194 1
|
||||||
|
230 1
|
||||||
|
236 1
|
||||||
|
254 1
|
||||||
|
274 1
|
||||||
|
277 1
|
||||||
|
344 1
|
||||||
|
350 1
|
||||||
|
360 3
|
||||||
|
370 1
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
この場合は「値 / カウント」表を優先して使用し、表より前のグラフ由来の
|
||||||
|
文字列は無視します。
|
||||||
|
|
||||||
* 実行方法
|
* 実行方法
|
||||||
|
|
||||||
#+begin_src sh
|
#+begin_src sh
|
||||||
./analyze_lab_assignment_survey.py googleform.txt
|
./analyze_lab_assignment_survey.py googleform.txt
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
* 出力
|
* 出力
|
||||||
実行すると、研究室ごとの集計結果が標準出力に表示されます。
|
|
||||||
|
|
||||||
出力例:
|
実行すると、研究室ごとの集計結果を標準出力に表示します。
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
|
----------------------------------------------------
|
||||||
|
回答者数: 98
|
||||||
研究室 在校生 編入生 GPA mean GPA std
|
研究室 在校生 編入生 GPA mean GPA std
|
||||||
----------------------------------------------------
|
----------------------------------------------------
|
||||||
1 9 0 359.6 38.4
|
1 7 0 345.1 50.7
|
||||||
2 6 0 269.3 66.3
|
2 5 1 303.4 63.0
|
||||||
3 8 1 358.7 42.7
|
3 7 0 351.6 69.0
|
||||||
6 3 0 343.7 58.5
|
6 7 0 354.0 32.9
|
||||||
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
|
#+end_example
|
||||||
|
|
||||||
|
=GPA std= は標本標準偏差です。対象が1人の場合は =-= と表示します。
|
||||||
|
|
||||||
* 在校生と編入生の判定
|
* 在校生と編入生の判定
|
||||||
|
|
||||||
このスクリプトでは、回答値について
|
解析時には、回答値について
|
||||||
|
|
||||||
#+begin_src python
|
#+begin_src python
|
||||||
df_["値"] > 10
|
df_lab["値"] > 10
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
を満たす回答を在校生の GPA データとして扱います。
|
を満たす回答を在校生の GPA データとして扱います。
|
||||||
|
|
||||||
したがって、
|
したがって、
|
||||||
|
|
||||||
- 値 > 10 : 在校生の GPA
|
- 値 > 10 : 在校生
|
||||||
- 値 <= 10 : 編入生
|
- 値 <= 10 : 編入生
|
||||||
|
|
||||||
として分類します。
|
として分類します。
|
||||||
|
|
||||||
|
現在のスクリプトでは、文字列 =編入生= を内部的に =0= として保存するため、
|
||||||
|
この条件で編入生として分類されます。
|
||||||
|
|
||||||
* GPA の統計量
|
* GPA の統計量
|
||||||
|
|
||||||
研究室ごとに、在校生のみを抽出して GPA の平均値と標準偏差を計算します。
|
研究室ごとに在校生のみを抽出し、GPA の平均値と標準偏差を計算します。
|
||||||
|
|
||||||
平均値:
|
平均値:
|
||||||
|
|
||||||
#+begin_src python
|
#+begin_src python
|
||||||
df__["値"].mean()
|
df_regular["値"].mean()
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
標準偏差:
|
標準偏差:
|
||||||
|
|
||||||
#+begin_src python
|
#+begin_src python
|
||||||
df__["値"].std()
|
df_regular["値"].std()
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
=pandas.Series.std()= を使用するため、標準偏差は標本標準偏差
|
||||||
|
(=ddof=1=)です。
|
||||||
|
|
||||||
|
* Excel 出力
|
||||||
|
|
||||||
|
=-o= または =--output= を指定すると、解析結果を Excel ファイルに保存します。
|
||||||
|
|
||||||
|
#+begin_src sh
|
||||||
|
./analyze_lab_assignment_survey.py googleform.txt -o result.xlsx
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Excel ファイルには以下のシートを作成します。
|
||||||
|
|
||||||
|
- =raw= : 抽出した研究室番号と回答値
|
||||||
|
- =summary= : 研究室ごとの集計結果
|
||||||
|
|
||||||
|
* 対応している入力形式
|
||||||
|
|
||||||
|
以下の形式に対応します。
|
||||||
|
|
||||||
|
- =研究室1= などの研究室番号
|
||||||
|
- 全角数字を含む研究室番号
|
||||||
|
- =7 件の回答=
|
||||||
|
- =GP7 件の回答=
|
||||||
|
- 整数値
|
||||||
|
- 小数値
|
||||||
|
- =365(修正済)= のような注記付き数値
|
||||||
|
- =編入生=
|
||||||
|
- 「値 / カウント」表
|
||||||
|
- Google Forms のグラフ由来の連結数字列
|
||||||
|
|
||||||
* 注意事項
|
* 注意事項
|
||||||
|
|
||||||
- 入力テキストは Google Forms の表示形式に依存します。
|
- 入力テキストは Google Forms の表示形式に依存します。
|
||||||
- Google Forms 側の HTML やコピー形式が変更された場合、正規表現の修正が必要になる可能性があります。
|
- Google Forms のコピー形式が変わった場合、正規表現の修正が必要になる可能性があります。
|
||||||
- 編入生の判定は「値が10以下」という運用上の仮定に基づいています。
|
- 編入生の判定は「値が10以下」という運用上の仮定に基づきます。
|
||||||
- GPA が実際に10以下となる別形式のデータを使用する場合は、判定条件を変更してください。
|
- GPA が実際に10以下となるデータ形式では判定条件を変更してください。
|
||||||
- 回答値の誤入力は自動修正しません。
|
- 回答値の誤入力は自動修正しません。
|
||||||
- 研究室番号が存在しても回答数が0件の場合、その研究室は出力から除外されます。
|
- 回答数と抽出数が一致しない場合は警告を表示します。
|
||||||
|
- 回答数が0件の研究室は出力しません。
|
||||||
|
|||||||
+156
-107
@@ -1,9 +1,79 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
import numpy as np
|
import argparse
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
|
ZEN_TO_ASCII = str.maketrans("0123456789", "0123456789")
|
||||||
|
|
||||||
|
|
||||||
|
def z2a(s):
|
||||||
|
"""全角数字を半角数字に変換する。"""
|
||||||
|
return s.translate(ZEN_TO_ASCII)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_values_from_body(body):
|
||||||
|
"""
|
||||||
|
研究室ごとの回答本文から GPA 値を取り出す。
|
||||||
|
|
||||||
|
- 「値 カウント」表がある場合は表を優先
|
||||||
|
- 表がない場合は 1 行 1 回答として読む
|
||||||
|
- 「編入生」は GPA としては読まない
|
||||||
|
- 「365(修正済)」のような値にも対応
|
||||||
|
"""
|
||||||
|
|
||||||
|
body = z2a(body)
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# Case 1: 「値 カウント」表がある場合
|
||||||
|
# --------------------------------------------------
|
||||||
|
m = re.search(r'^値\s+カウント\s*$', body, flags=re.MULTILINE)
|
||||||
|
|
||||||
|
if m:
|
||||||
|
table_part = body[m.end():]
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
return values
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# Case 2: 回答値が1行ずつ並んでいる場合
|
||||||
|
# --------------------------------------------------
|
||||||
|
values = []
|
||||||
|
|
||||||
|
for line in body.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if "編入生" in line:
|
||||||
|
values.append(0.0)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if "この質問にはまだ回答がありません" in line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 例: 365(修正済)
|
||||||
|
m = re.match(r'^(-?\d+(?:\.\d+)?)', line)
|
||||||
|
if m:
|
||||||
|
values.append(float(m.group(1)))
|
||||||
|
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
def parse_google_form_results(text):
|
def parse_google_form_results(text):
|
||||||
"""
|
"""
|
||||||
Google Forms のアンケート結果をコピペしたテキストから、
|
Google Forms のアンケート結果をコピペしたテキストから、
|
||||||
@@ -11,21 +81,24 @@ def parse_google_form_results(text):
|
|||||||
研究室番号 | 値
|
研究室番号 | 値
|
||||||
|
|
||||||
の DataFrame を生成する。
|
の DataFrame を生成する。
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
text : str
|
|
||||||
Google Forms 結果ページからコピーしたテキスト
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
pandas.DataFrame
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 「第一希希望研究室...」の集計部分は除外したいので、
|
text = z2a(text)
|
||||||
# 最初の個別研究室ブロックから開始する
|
|
||||||
|
# 「研究室第1希望」などの集計部分を除外し、
|
||||||
|
# 最初の個別研究室ブロックから開始する。
|
||||||
|
#
|
||||||
|
# 以下の両方に対応:
|
||||||
|
#
|
||||||
|
# 研究室1
|
||||||
|
# GP7 件の回答
|
||||||
|
#
|
||||||
|
# 研究室1
|
||||||
|
# 7 件の回答
|
||||||
m = re.search(
|
m = re.search(
|
||||||
r'^研究室\s*[0-90-9]+.*?\n[0-90-9]+\s*件の回答',
|
r'^研究室\s*[0-9]+[^\n]*\n'
|
||||||
|
r'(?:[^\d\n]*?)'
|
||||||
|
r'[0-9]+\s*件の回答',
|
||||||
text,
|
text,
|
||||||
flags=re.MULTILINE
|
flags=re.MULTILINE
|
||||||
)
|
)
|
||||||
@@ -37,80 +110,31 @@ def parse_google_form_results(text):
|
|||||||
|
|
||||||
# 各研究室ブロックを取得
|
# 各研究室ブロックを取得
|
||||||
pattern = re.compile(
|
pattern = re.compile(
|
||||||
r'^研究室\s*([0-90-9]+).*?\n'
|
r'^研究室\s*([0-9]+)[^\n]*\n'
|
||||||
r'([0-90-9]+)\s*件の回答\s*\n'
|
r'(?:[^\d\n]*?)'
|
||||||
|
r'([0-9]+)\s*件の回答\s*\n'
|
||||||
r'(.*?)'
|
r'(.*?)'
|
||||||
r'(?=^研究室\s*[0-90-9]+.*?\n[0-90-9]+\s*件の回答|\Z)',
|
r'(?='
|
||||||
|
r'^研究室\s*[0-9]+[^\n]*\n'
|
||||||
|
r'(?:[^\d\n]*?)'
|
||||||
|
r'[0-9]+\s*件の回答'
|
||||||
|
r'|\Z'
|
||||||
|
r')',
|
||||||
flags=re.MULTILINE | re.DOTALL
|
flags=re.MULTILINE | re.DOTALL
|
||||||
)
|
)
|
||||||
|
|
||||||
rows = []
|
rows = []
|
||||||
|
|
||||||
for match in pattern.finditer(text):
|
for match in pattern.finditer(text):
|
||||||
lab_no_str = match.group(1)
|
lab_no = int(match.group(1))
|
||||||
n_answers_str = match.group(2)
|
n_answers = int(match.group(2))
|
||||||
body = match.group(3).strip()
|
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:
|
if n_answers == 0:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# --------------------------------------------------
|
values = extract_values_from_body(body)
|
||||||
# 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:
|
if len(values) != n_answers:
|
||||||
print(
|
print(
|
||||||
f"警告: 研究室{lab_no}: "
|
f"警告: 研究室{lab_no}: "
|
||||||
@@ -125,61 +149,86 @@ def parse_google_form_results(text):
|
|||||||
|
|
||||||
df = pd.DataFrame(rows)
|
df = pd.DataFrame(rows)
|
||||||
|
|
||||||
# 整数値だけなら見やすくする
|
if not df.empty and (df["値"] % 1 == 0).all():
|
||||||
if not df.empty:
|
|
||||||
if (df["値"] % 1 == 0).all():
|
|
||||||
df["値"] = df["値"].astype(int)
|
df["値"] = df["値"].astype(int)
|
||||||
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
def summarize_by_lab(df):
|
||||||
import argparse
|
"""研究室ごとの在校生数・編入生数・GPA平均・標準偏差を計算する。"""
|
||||||
|
|
||||||
par = argparse.ArgumentParser(description="test")
|
rows = []
|
||||||
par.add_argument('googleform_str')
|
|
||||||
args = par.parse_args()
|
|
||||||
|
|
||||||
# コピペしたテキストをファイルに保存しておく
|
for lab_no in sorted(df["研究室番号"].unique()):
|
||||||
with open(args.googleform_str, encoding="utf-8") as f:
|
df_lab = df.loc[df["研究室番号"] == lab_no].copy()
|
||||||
text = f.read()
|
|
||||||
|
|
||||||
df = parse_google_form_results(text)
|
# 編入生は 0 として入れてある。
|
||||||
df_r = pd.DataFrame()
|
df_regular = df_lab.loc[df_lab["値"] > 10]
|
||||||
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()
|
rows.append({
|
||||||
df_show.index.name = "研究室"
|
"研究室": lab_no,
|
||||||
df_show["GPA mean"] = df_show["GPA mean"].round(1)
|
"在校生": df_regular.shape[0],
|
||||||
df_show["GPA std"] = df_show["GPA std"].round(1)
|
"編入生": df_lab.shape[0] - df_regular.shape[0],
|
||||||
|
"GPA mean": df_regular["値"].mean(),
|
||||||
|
"GPA std": df_regular["値"].std(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return pd.DataFrame(rows).set_index("研究室")
|
||||||
|
|
||||||
|
|
||||||
|
def print_summary(df, df_r):
|
||||||
print("-" * 52)
|
print("-" * 52)
|
||||||
print("回答者数:", df.shape[0])
|
print("回答者数:", df.shape[0])
|
||||||
print(f"{'研究室':>6} "
|
print(
|
||||||
|
f"{'研究室':>6} "
|
||||||
f"{'在校生':>6} "
|
f"{'在校生':>6} "
|
||||||
f"{'編入生':>6} "
|
f"{'編入生':>6} "
|
||||||
f"{'GPA mean':>10} "
|
f"{'GPA mean':>10} "
|
||||||
f"{'GPA std':>10}"
|
f"{'GPA std':>10}"
|
||||||
)
|
)
|
||||||
print("-" * 52)
|
print("-" * 52)
|
||||||
|
|
||||||
for n, row in df_r.iterrows():
|
for n, row in df_r.iterrows():
|
||||||
mean = f"{row['GPA mean']:.1f}"
|
mean = "-" if pd.isna(row["GPA mean"]) else f"{row['GPA mean']:.1f}"
|
||||||
std = "-" if pd.isna(row["GPA std"]) else f"{row['GPA std']:.1f}"
|
std = "-" if pd.isna(row["GPA std"]) else f"{row['GPA std']:.1f}"
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f"{n:>6} "
|
f"{n:>6} "
|
||||||
f"{int(row['在校生']):>8} "
|
f"{int(row['在校生']):>8} "
|
||||||
f"{int(row['編入生']):>8} "
|
f"{int(row['編入生']):>8} "
|
||||||
f"{mean:>10} "
|
f"{mean:>10} "
|
||||||
f"{std:>10}")
|
f"{std:>10}"
|
||||||
|
)
|
||||||
|
|
||||||
# outfile = __file__.replace(".py", ".xlsx")
|
|
||||||
# df.to_excel(outfile)
|
def main():
|
||||||
# print(f"{outfile} was created.")
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Google Forms のコピペ結果から研究室別 GPA を集計する"
|
||||||
|
)
|
||||||
|
parser.add_argument("googleform_str", help="Google Forms からコピーしたテキストファイル")
|
||||||
|
parser.add_argument(
|
||||||
|
"-o", "--output",
|
||||||
|
help="Excel 出力ファイル名。指定しない場合は出力しない"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
with open(args.googleform_str, encoding="utf-8") as f:
|
||||||
|
text = f.read()
|
||||||
|
|
||||||
|
df = parse_google_form_results(text)
|
||||||
|
df_r = summarize_by_lab(df)
|
||||||
|
|
||||||
|
print_summary(df, df_r)
|
||||||
|
|
||||||
|
if args.output:
|
||||||
|
with pd.ExcelWriter(args.output) as writer:
|
||||||
|
df.to_excel(writer, sheet_name="raw")
|
||||||
|
df_r.to_excel(writer, sheet_name="summary")
|
||||||
|
|
||||||
|
print(f"{args.output} was created.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|||||||
Reference in New Issue
Block a user