877 lines
20 KiB
Python
Executable File
877 lines
20 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
import argparse
|
|
import re
|
|
|
|
import numpy as np
|
|
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):
|
|
"""
|
|
研究室ごとの回答本文からGP値を取り出す。
|
|
|
|
- 「値 カウント」表がある場合は表を優先
|
|
- 表がない場合は1行1回答として読む
|
|
- 「編入生」はGPとしては読まず、内部値0として扱う
|
|
- 「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):
|
|
"""
|
|
Google Formsのアンケート結果をコピペしたテキストから、
|
|
|
|
研究室番号 | 値
|
|
|
|
のDataFrameを生成する。
|
|
|
|
回答者が0人の研究室についても、
|
|
|
|
研究室番号 | NaN
|
|
|
|
という行を作成してDataFrameに残す。
|
|
"""
|
|
|
|
text = z2a(text)
|
|
|
|
# 「第一希望研究室」などの集計部分を除外し、
|
|
# 最初の個別研究室ブロックから開始する。
|
|
#
|
|
# 以下の両方に対応する。
|
|
#
|
|
# 研究室1
|
|
# GP7件の回答
|
|
#
|
|
# 研究室1
|
|
# 7件の回答
|
|
m = re.search(
|
|
r"^研究室\s*[0-9]+[^\n]*\n"
|
|
r"(?:[^\d\n]*?)"
|
|
r"[0-9]+\s*件の回答",
|
|
text,
|
|
flags=re.MULTILINE,
|
|
)
|
|
|
|
if not m:
|
|
raise ValueError("個別研究室ブロックが見つかりません")
|
|
|
|
text = text[m.start():]
|
|
|
|
# 各研究室ブロックを取得する。
|
|
pattern = re.compile(
|
|
r"^研究室\s*([0-9]+)[^\n]*\n"
|
|
r"(?:[^\d\n]*?)"
|
|
r"([0-9]+)\s*件の回答\s*\n"
|
|
r"(.*?)"
|
|
r"(?="
|
|
r"^研究室\s*[0-9]+[^\n]*\n"
|
|
r"(?:[^\d\n]*?)"
|
|
r"[0-9]+\s*件の回答"
|
|
r"|\Z"
|
|
r")",
|
|
flags=re.MULTILINE | re.DOTALL,
|
|
)
|
|
|
|
rows = []
|
|
|
|
for match in pattern.finditer(text):
|
|
lab_no = int(match.group(1))
|
|
n_answers = int(match.group(2))
|
|
body = match.group(3).strip()
|
|
|
|
# 回答者が0人の研究室もDataFrameに残す。
|
|
#
|
|
# 編入生を表す内部値0と区別するため、
|
|
# 回答者なしはNaNで表現する。
|
|
if n_answers == 0:
|
|
rows.append(
|
|
{
|
|
"研究室番号": lab_no,
|
|
"値": np.nan,
|
|
}
|
|
)
|
|
continue
|
|
|
|
values = extract_values_from_body(body)
|
|
|
|
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,
|
|
columns=["研究室番号", "値"],
|
|
)
|
|
|
|
if df.empty:
|
|
return df
|
|
|
|
# すべての有効値が整数の場合は、NaNを保持できる
|
|
# pandasのnullable整数型Int64に変換する。
|
|
non_na_values = df["値"].dropna()
|
|
|
|
if (
|
|
not non_na_values.empty
|
|
and (non_na_values % 1 == 0).all()
|
|
):
|
|
df["値"] = df["値"].astype("Int64")
|
|
|
|
return df
|
|
|
|
|
|
def summarize_by_lab(df):
|
|
"""
|
|
研究室ごとの在校生数・編入生数・GP平均・標準偏差を計算する。
|
|
|
|
- NaNは回答者0人を表すため、人数には含めない
|
|
- 値が10より大きい回答を在校生として扱う
|
|
- 値が10以下の回答を編入生として扱う
|
|
"""
|
|
|
|
required_columns = {
|
|
"研究室番号",
|
|
"値",
|
|
}
|
|
|
|
missing_columns = required_columns - set(df.columns)
|
|
|
|
if missing_columns:
|
|
raise KeyError(
|
|
"必須列がありません: "
|
|
+ ", ".join(sorted(missing_columns))
|
|
)
|
|
|
|
rows = []
|
|
|
|
for lab_no in sorted(df["研究室番号"].unique()):
|
|
df_lab = df.loc[
|
|
df["研究室番号"] == lab_no
|
|
].copy()
|
|
|
|
# 回答者0人を表すNaN行を除外する。
|
|
df_answers = df_lab.loc[
|
|
df_lab["値"].notna()
|
|
].copy()
|
|
|
|
# 在校生と編入生を分類する。
|
|
df_regular = df_answers.loc[
|
|
df_answers["値"] > 10
|
|
]
|
|
|
|
df_transfer = df_answers.loc[
|
|
df_answers["値"] <= 10
|
|
]
|
|
|
|
rows.append(
|
|
{
|
|
"研究室": lab_no,
|
|
"回答者数": df_answers.shape[0],
|
|
"在校生": df_regular.shape[0],
|
|
"編入生": df_transfer.shape[0],
|
|
"GP mean": df_regular["値"].mean(),
|
|
"GP std": df_regular["値"].std(),
|
|
}
|
|
)
|
|
|
|
return pd.DataFrame(rows).set_index("研究室")
|
|
|
|
|
|
def print_summary(df, df_r):
|
|
"""研究室ごとの集計結果を端末に表示する。"""
|
|
|
|
# NaNは回答者0人の研究室を表すため数えない。
|
|
n_respondents = int(df["値"].notna().sum())
|
|
|
|
print("-" * 66)
|
|
print("回答者数:", n_respondents)
|
|
|
|
print(
|
|
f"{'研究室':>6} "
|
|
f"{'回答者':>8} "
|
|
f"{'在校生':>8} "
|
|
f"{'編入生':>8} "
|
|
f"{'GP mean':>10} "
|
|
f"{'GP std':>10}"
|
|
)
|
|
|
|
print("-" * 66)
|
|
|
|
for lab_no, row in df_r.iterrows():
|
|
mean = (
|
|
"-"
|
|
if pd.isna(row["GP mean"])
|
|
else f"{row['GP mean']:.1f}"
|
|
)
|
|
|
|
std = (
|
|
"-"
|
|
if pd.isna(row["GP std"])
|
|
else f"{row['GP std']:.1f}"
|
|
)
|
|
|
|
print(
|
|
f"{lab_no:>6} "
|
|
f"{int(row['回答者数']):>8} "
|
|
f"{int(row['在校生']):>8} "
|
|
f"{int(row['編入生']):>8} "
|
|
f"{mean:>10} "
|
|
f"{std:>10}"
|
|
)
|
|
|
|
print("-" * 66)
|
|
|
|
|
|
def apply_plot_style():
|
|
"""
|
|
論文図として使いやすいmatplotlibの描画設定を適用する。
|
|
|
|
仕様:
|
|
- seabornやscienceplotsには依存しない
|
|
- タイトルは付けず、軸ラベルと目盛だけで情報を伝える
|
|
- フォントサイズ、線幅、余白、保存時DPIを論文図向けに調整する
|
|
- カラーマップは既定でtab10を使う
|
|
"""
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
plt.rcParams.update(
|
|
{
|
|
"figure.dpi": 120,
|
|
"savefig.dpi": 300,
|
|
"savefig.bbox": "tight",
|
|
"font.family": "sans-serif",
|
|
"font.sans-serif": [
|
|
"Arial",
|
|
"Helvetica",
|
|
"DejaVu Sans",
|
|
],
|
|
"font.size": 11,
|
|
"axes.labelsize": 12,
|
|
"axes.linewidth": 1.0,
|
|
"axes.spines.top": False,
|
|
"axes.spines.right": False,
|
|
"xtick.labelsize": 10,
|
|
"ytick.labelsize": 10,
|
|
"xtick.direction": "out",
|
|
"ytick.direction": "out",
|
|
"xtick.major.size": 4,
|
|
"ytick.major.size": 4,
|
|
"xtick.major.width": 1.0,
|
|
"ytick.major.width": 1.0,
|
|
"legend.frameon": False,
|
|
"pdf.fonttype": 42,
|
|
"ps.fonttype": 42,
|
|
}
|
|
)
|
|
|
|
|
|
def filter_plot_data(data, lower=10, upper=None):
|
|
"""
|
|
GPの描画対象データを抽出する。
|
|
|
|
Parameters
|
|
----------
|
|
data : pandas.DataFrame
|
|
「研究室番号」と「値」列を持つデータフレーム。
|
|
lower : float, default 10
|
|
この値より大きい回答を描画対象にする。
|
|
既定値では編入生と回答者なしのNaNを除く。
|
|
upper : float or None, default None
|
|
指定した場合、この値未満の回答だけを描画対象にする。
|
|
|
|
Returns
|
|
-------
|
|
pandas.DataFrame
|
|
フィルタ後のデータ。元データは変更しない。
|
|
"""
|
|
|
|
required_columns = {
|
|
"研究室番号",
|
|
"値",
|
|
}
|
|
|
|
missing_columns = required_columns - set(data.columns)
|
|
|
|
if missing_columns:
|
|
raise KeyError(
|
|
"必須列がありません: "
|
|
+ ", ".join(sorted(missing_columns))
|
|
)
|
|
|
|
# NaNは比較結果がFalseになるため、ここで自動的に除外される。
|
|
df = data.loc[
|
|
data["値"] > lower,
|
|
["研究室番号", "値"],
|
|
].copy()
|
|
|
|
if upper is not None:
|
|
df = df.loc[
|
|
df["値"] < upper
|
|
].copy()
|
|
|
|
if df.empty:
|
|
raise ValueError(
|
|
"描画対象のデータがありません。"
|
|
"lower/upperを確認してください。"
|
|
)
|
|
|
|
# matplotlib/numpyで扱いやすい通常のfloat型に変換する。
|
|
df["値"] = df["値"].astype(float)
|
|
|
|
return df
|
|
|
|
|
|
def make_histogram_bins(values, bin_width):
|
|
"""
|
|
GPヒストグラム用のビン境界を作成する。
|
|
|
|
仕様:
|
|
- 最小値側はbin_widthの倍数に切り下げる
|
|
- 最大値側はbin_widthの倍数に切り上げる
|
|
- 最大値が最後のビンに入るようにする
|
|
- bin_widthは正の数でなければならない
|
|
"""
|
|
|
|
if bin_width <= 0:
|
|
raise ValueError(
|
|
"bin_widthは正の数を指定してください。"
|
|
)
|
|
|
|
min_edge = (
|
|
np.floor(values.min() / bin_width)
|
|
* bin_width
|
|
)
|
|
|
|
max_edge = (
|
|
np.ceil(values.max() / bin_width)
|
|
* bin_width
|
|
+ bin_width
|
|
)
|
|
|
|
return np.arange(
|
|
min_edge,
|
|
max_edge,
|
|
bin_width,
|
|
)
|
|
|
|
|
|
def style_axis(ax, grid_axis="y"):
|
|
"""
|
|
軸まわりの体裁を統一する。
|
|
|
|
仕様:
|
|
- 上枠と右枠を消す
|
|
- 目盛を外向きにする
|
|
- 指定軸方向に薄いグリッドを入れる
|
|
"""
|
|
|
|
ax.spines["top"].set_visible(False)
|
|
ax.spines["right"].set_visible(False)
|
|
|
|
ax.tick_params(
|
|
direction="out",
|
|
length=4,
|
|
width=1,
|
|
)
|
|
|
|
ax.grid(
|
|
axis=grid_axis,
|
|
color="0.88",
|
|
linewidth=0.8,
|
|
)
|
|
|
|
ax.set_axisbelow(True)
|
|
|
|
|
|
def draw_lab_boxplot(ax, df, labs, color, rng):
|
|
"""
|
|
研究室別GP分布を箱ひげ図と実測点で描画する。
|
|
|
|
仕様:
|
|
- 箱ひげ図は外れ値を非表示にする
|
|
- 実測点を重ねて全回答の分布を見せる
|
|
- 実測点には固定乱数のjitterを与える
|
|
- x軸のカテゴリ順はlabsの順序に従う
|
|
"""
|
|
|
|
positions = np.arange(
|
|
1,
|
|
len(labs) + 1,
|
|
)
|
|
|
|
grouped_values = [
|
|
df.loc[
|
|
df["研究室番号"] == lab,
|
|
"値",
|
|
].to_numpy(dtype=float)
|
|
for lab in labs
|
|
]
|
|
|
|
box = ax.boxplot(
|
|
grouped_values,
|
|
positions=positions,
|
|
widths=0.55,
|
|
patch_artist=True,
|
|
showfliers=False,
|
|
medianprops={
|
|
"color": "black",
|
|
"linewidth": 1.2,
|
|
},
|
|
boxprops={
|
|
"facecolor": color,
|
|
"edgecolor": "black",
|
|
"linewidth": 1.0,
|
|
},
|
|
whiskerprops={
|
|
"color": "black",
|
|
"linewidth": 1.0,
|
|
},
|
|
capprops={
|
|
"color": "black",
|
|
"linewidth": 1.0,
|
|
},
|
|
)
|
|
|
|
for patch in box["boxes"]:
|
|
patch.set_alpha(0.55)
|
|
|
|
for x_position, values in zip(
|
|
positions,
|
|
grouped_values,
|
|
):
|
|
jitter = rng.uniform(
|
|
-0.16,
|
|
0.16,
|
|
size=len(values),
|
|
)
|
|
|
|
ax.scatter(
|
|
np.full(len(values), x_position) + jitter,
|
|
values,
|
|
s=18,
|
|
color="black",
|
|
alpha=0.45,
|
|
linewidths=0,
|
|
zorder=3,
|
|
)
|
|
|
|
ax.set_xlim(
|
|
0.4,
|
|
len(labs) + 0.6,
|
|
)
|
|
|
|
ax.set_xticks(positions)
|
|
|
|
ax.set_xticklabels(
|
|
[str(lab) for lab in labs]
|
|
)
|
|
|
|
ax.set_xlabel("Laboratory")
|
|
ax.set_ylabel("GP")
|
|
|
|
|
|
def annotate_target_values(
|
|
ax,
|
|
target_values,
|
|
color,
|
|
):
|
|
"""
|
|
ヒストグラム上に対象研究室のGPを縦線と数値で示す。
|
|
|
|
仕様:
|
|
- 同じGP値は1本の線にまとめる
|
|
- ラベルは軸上端の内側に置く
|
|
- 近い値が複数ある場合はラベル高さをずらす
|
|
"""
|
|
|
|
unique_values = (
|
|
pd.Series(target_values)
|
|
.value_counts()
|
|
.sort_index()
|
|
)
|
|
|
|
for i, (value, count) in enumerate(
|
|
unique_values.items()
|
|
):
|
|
ax.axvline(
|
|
value,
|
|
color=color,
|
|
linewidth=1.3,
|
|
alpha=0.85,
|
|
linestyle="--",
|
|
)
|
|
|
|
if count == 1:
|
|
label = f"{value:g}"
|
|
else:
|
|
label = f"{value:g} x{count}"
|
|
|
|
ax.text(
|
|
value,
|
|
0.96 - 0.11 * (i % 3),
|
|
label,
|
|
transform=ax.get_xaxis_transform(),
|
|
horizontalalignment="center",
|
|
verticalalignment="top",
|
|
rotation=90,
|
|
color=color,
|
|
fontsize=9,
|
|
bbox={
|
|
"facecolor": "white",
|
|
"edgecolor": "none",
|
|
"alpha": 0.75,
|
|
"pad": 1.2,
|
|
},
|
|
)
|
|
|
|
|
|
def draw_gp_histogram(
|
|
ax,
|
|
df,
|
|
target_values,
|
|
bin_width,
|
|
color,
|
|
target_color,
|
|
):
|
|
"""
|
|
全研究室のGPヒストグラムを描画する。
|
|
|
|
仕様:
|
|
- bin_widthごとの頻度を表示する
|
|
- 対象研究室のGPは縦線で示す
|
|
- 研究室別箱ひげ図と同じ主色を使う
|
|
"""
|
|
|
|
bins = make_histogram_bins(
|
|
df["値"],
|
|
bin_width,
|
|
)
|
|
|
|
ax.hist(
|
|
df["値"],
|
|
bins=bins,
|
|
color=color,
|
|
alpha=0.62,
|
|
edgecolor="black",
|
|
linewidth=0.8,
|
|
)
|
|
|
|
if len(target_values) > 0:
|
|
annotate_target_values(
|
|
ax,
|
|
target_values,
|
|
target_color,
|
|
)
|
|
|
|
ax.set_xlabel("GP")
|
|
ax.set_ylabel("Frequency")
|
|
|
|
|
|
def plot_data(
|
|
data,
|
|
lower=10,
|
|
upper=None,
|
|
target_lab=None,
|
|
bin_width=10,
|
|
):
|
|
"""
|
|
研究室配属希望調査のGP分布を描画する。
|
|
|
|
Parameters
|
|
----------
|
|
data : pandas.DataFrame
|
|
「研究室番号」と「値」列を持つデータフレーム。
|
|
lower : float, default 10
|
|
値がlowerより大きい回答だけを描く。
|
|
upper : float or None, default None
|
|
GPの上限。Noneの場合は上限を設けない。
|
|
target_lab : int, default None
|
|
GP値を縦線表示する研究室番号。
|
|
bin_width : float, default 10
|
|
ヒストグラムのビン幅。
|
|
|
|
Returns
|
|
-------
|
|
tuple
|
|
生成したFigureと2個のAxes。
|
|
|
|
仕様:
|
|
- matplotlibとpandas/numpyのみで描画する
|
|
- 図タイトルは付けない
|
|
- 上段に研究室別箱ひげ図と個別点を描く
|
|
- 下段に全体GPヒストグラムを描く
|
|
- 編入生および回答者なしのNaNは描画しない
|
|
"""
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
df = filter_plot_data(
|
|
data.copy(),
|
|
lower=lower,
|
|
upper=upper,
|
|
)
|
|
|
|
labs = sorted(
|
|
df["研究室番号"].unique()
|
|
)
|
|
|
|
target_values = df.loc[
|
|
df["研究室番号"] == target_lab,
|
|
"値",
|
|
].to_numpy(dtype=float)
|
|
|
|
apply_plot_style()
|
|
|
|
colors = plt.get_cmap("tab10").colors
|
|
|
|
rng = np.random.default_rng(
|
|
20260709
|
|
)
|
|
|
|
fig, axs = plt.subplots(
|
|
2,
|
|
1,
|
|
figsize=(8.0, 6.2),
|
|
gridspec_kw={
|
|
"height_ratios": [1.1, 1],
|
|
},
|
|
constrained_layout=True,
|
|
)
|
|
|
|
draw_lab_boxplot(
|
|
axs[0],
|
|
df,
|
|
labs,
|
|
colors[0],
|
|
rng,
|
|
)
|
|
|
|
draw_gp_histogram(
|
|
axs[1],
|
|
df,
|
|
target_values,
|
|
bin_width,
|
|
colors[0],
|
|
colors[3],
|
|
)
|
|
|
|
describe_text = (
|
|
df["値"]
|
|
.describe()
|
|
.to_string(
|
|
float_format=lambda x: f"{x:.1f}"
|
|
)
|
|
)
|
|
|
|
# 下段ヒストグラムの左上にdescribe()を表示する。
|
|
axs[1].text(
|
|
0.01,
|
|
0.99,
|
|
describe_text,
|
|
transform=axs[1].transAxes,
|
|
horizontalalignment="left",
|
|
verticalalignment="top",
|
|
fontsize=8,
|
|
fontfamily="monospace",
|
|
bbox={
|
|
"facecolor": "white",
|
|
"edgecolor": "gray",
|
|
"alpha": 0.85,
|
|
"boxstyle": "round,pad=0.2",
|
|
},
|
|
zorder=1,
|
|
)
|
|
|
|
for ax in axs:
|
|
style_axis(ax)
|
|
|
|
plt.show()
|
|
|
|
return fig, axs
|
|
|
|
|
|
def make_output_filename(
|
|
input_filename,
|
|
extension,
|
|
):
|
|
"""
|
|
入力ファイル名の拡張子を変更した出力ファイル名を作る。
|
|
"""
|
|
|
|
if "." in input_filename:
|
|
base = input_filename.rsplit(".", 1)[0]
|
|
else:
|
|
base = input_filename
|
|
|
|
return base + extension
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description=(
|
|
"Google Formsのコピペ結果から"
|
|
"研究室別GPを集計する"
|
|
)
|
|
)
|
|
|
|
parser.add_argument(
|
|
"googleform_str",
|
|
help=(
|
|
"Google Formsからコピーした"
|
|
"テキストファイル"
|
|
),
|
|
)
|
|
|
|
parser.add_argument(
|
|
"-o",
|
|
"--output",
|
|
help=(
|
|
"Excel出力ファイル名。"
|
|
"指定しない場合は出力しない"
|
|
),
|
|
)
|
|
|
|
parser.add_argument(
|
|
"-p",
|
|
"--plot",
|
|
action="store_true",
|
|
help=(
|
|
"各種plotを作成する。"
|
|
"指定しない場合は作成しない"
|
|
),
|
|
)
|
|
|
|
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.plot:
|
|
fig, axs = plot_data(df)
|
|
|
|
plot_filename = make_output_filename(
|
|
args.googleform_str,
|
|
".png",
|
|
)
|
|
|
|
fig.savefig(
|
|
plot_filename,
|
|
dpi=300,
|
|
)
|
|
|
|
print(
|
|
f"{plot_filename} was created."
|
|
)
|
|
|
|
if args.output:
|
|
with pd.ExcelWriter(args.output) as writer:
|
|
df.to_excel(
|
|
writer,
|
|
sheet_name="raw",
|
|
index=False,
|
|
)
|
|
|
|
df_r.to_excel(
|
|
writer,
|
|
sheet_name="summary",
|
|
)
|
|
|
|
print(
|
|
f"{args.output} was created."
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|