This commit is contained in:
2026-04-12 21:40:41 +08:00
parent 2399f0056d
commit df34c1e9bc
5 changed files with 890 additions and 5 deletions
+302
View File
@@ -0,0 +1,302 @@
import tkinter as tk
from tkinter import ttk, messagebox
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import os
# 定义常量
SUBJECTS = ['语文', '数学', '英语', '物理', '化学', '生物']
# 设置绘图字体
plt.rcParams['font.sans-serif'] = ['SimHei'] # 这里假设Windows环境,Linux/Mac可能需要更改
plt.rcParams['axes.unicode_minus'] = False
class GradeSystemApp:
def __init__(self, root):
self.root = root
self.root.title("学生成绩分析系统 V2.0")
self.root.geometry("800x600")
# 数据存储列表
self.student_data_list = []
self.setup_ui()
def setup_ui(self):
# --- 顶部:录入区域 ---
input_frame = tk.LabelFrame(self.root, text="成绩录入", padx=10, pady=10)
input_frame.pack(fill="x", padx=10, pady=5)
# 学号录入
tk.Label(input_frame, text="学号:").grid(row=0, column=0, padx=5, pady=5)
self.entry_id = tk.Entry(input_frame, width=15)
self.entry_id.grid(row=0, column=1, padx=5, pady=5)
# 各科目录入框字典
self.score_entries = {}
row = 1
col = 0
for i, subj in enumerate(SUBJECTS):
tk.Label(input_frame, text=f"{subj}:").grid(row=row, column=col * 2, padx=5, pady=5)
entry = tk.Entry(input_frame, width=10)
entry.grid(row=row, column=col * 2 + 1, padx=5, pady=5)
self.score_entries[subj] = entry
# 每行显示3个科目,换行
col += 1
if col > 2:
col = 0
row += 1
# 按钮区域
btn_frame = tk.Frame(input_frame)
btn_frame.grid(row=row + 1, column=0, columnspan=6, pady=10)
tk.Button(btn_frame, text="添加学生", command=self.add_student, bg="#e1f5fe", width=12).pack(side=tk.LEFT,
padx=10)
tk.Button(btn_frame, text="清空输入框", command=self.clear_entries, width=12).pack(side=tk.LEFT, padx=10)
tk.Button(btn_frame, text="生成分析报告", command=self.generate_report, bg="#4caf50", fg="white",
width=15).pack(side=tk.LEFT, padx=10)
# --- 中部:数据展示区域 (Treeview) ---
list_frame = tk.LabelFrame(self.root, text="已录入学生列表 (可选中并删除)", padx=10, pady=10)
list_frame.pack(fill="both", expand=True, padx=10, pady=5)
columns = ['学号'] + SUBJECTS
self.tree = ttk.Treeview(list_frame, columns=columns, show='headings', height=10)
# 设置表头和列宽
for col in columns:
self.tree.heading(col, text=col)
self.tree.column(col, width=80, anchor='center')
# 添加滚动条
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview)
self.tree.configure(yscroll=scrollbar.set)
self.tree.pack(side=tk.LEFT, fill="both", expand=True)
scrollbar.pack(side=tk.RIGHT, fill="y")
# 绑定删除事件(双击或按钮,这里用按钮)
del_btn = tk.Button(list_frame, text="删除选中行", command=self.delete_selected, bg="#ffcdd2")
del_btn.pack(side=tk.BOTTOM, fill="x", pady=5)
def add_student(self):
stu_id = self.entry_id.get().strip()
if not stu_id:
messagebox.showwarning("提示", "请输入学号!")
return
# 检查ID是否重复
for data in self.student_data_list:
if data['ID'] == stu_id:
messagebox.showerror("错误", f"学号 {stu_id} 已存在!")
return
# 获取分数
row_data = {'ID': stu_id}
display_values = [stu_id]
try:
for subj in SUBJECTS:
val = self.score_entries[subj].get().strip()
if not val:
messagebox.showwarning("提示", f"请输入 {subj} 成绩!")
return
score = float(val)
if score < 0 or score > 150: # 简单校验
messagebox.showwarning("提示", f"{subj} 成绩不合理,请检查!")
return
row_data[subj] = score
display_values.append(score)
# 保存数据
self.student_data_list.append(row_data)
# 更新界面表格
self.tree.insert('', tk.END, values=display_values)
# 清空输入并将焦点回到学号框
self.clear_entries()
self.entry_id.focus_set()
except ValueError:
messagebox.showerror("错误", "成绩必须是数字!")
def delete_selected(self):
selected_item = self.tree.selection()
if not selected_item:
return
for item in selected_item:
values = self.tree.item(item, 'values')
stu_id = values[0] # 学号是第一列
# 从数据源删除
self.student_data_list = [d for d in self.student_data_list if d['ID'] != stu_id]
# 从UI删除
self.tree.delete(item)
def clear_entries(self):
self.entry_id.delete(0, tk.END)
for entry in self.score_entries.values():
entry.delete(0, tk.END)
def generate_report(self):
if not self.student_data_list:
messagebox.showinfo("提示", "当前没有数据,无法生成报告。")
return
try:
# 转换为DataFrame
df = pd.DataFrame(self.student_data_list)
df.set_index('ID', inplace=True)
# 确保保存报告的文件夹存在
report_dir = "学生成绩报告单"
if not os.path.exists(report_dir):
os.makedirs(report_dir)
# ========================
# 1. 学生维度分析
# ========================
student_stats = df.copy()
student_stats['总分'] = student_stats[SUBJECTS].sum(axis=1)
student_stats['平均分'] = student_stats[SUBJECTS].mean(axis=1).round(1)
student_stats['成绩波动(标准差)'] = student_stats[SUBJECTS].std(axis=1).round(2)
def get_best_worst(row):
scores = row[SUBJECTS]
best_s = scores.idxmax()
worst_s = scores.idxmin()
return pd.Series([f"{best_s}({scores[best_s]})", f"{worst_s}({scores[worst_s]})"])
student_stats[['最好科目', '最差科目']] = student_stats.apply(get_best_worst, axis=1)
# 排名
student_stats = student_stats.sort_values(by='总分', ascending=False)
student_stats.insert(0, '排名', range(1, len(student_stats) + 1))
# 导出 CSV 1
student_stats.to_csv('1_学生总成绩排名表.csv', encoding='utf-8-sig')
# ========================
# 2. 科目维度分析
# ========================
subject_stats_list = []
bins = range(0, 101, 10) # 0-10, ..., 90-100 (不含101,需要特殊处理)
for subj in SUBJECTS:
s_data = df[subj]
excellent = (s_data >= 90).sum()
passing = (s_data >= 60).sum()
count = len(s_data)
# 分布统计
dist_str = []
for i in range(0, 100, 10):
if i == 90:
# 90-100 (包含100)
c = ((s_data >= 90) & (s_data <= 200)).sum() # 稍微放大上限容错
dist_str.append(f"90分以上:{c}")
else:
c = ((s_data >= i) & (s_data < i + 10)).sum()
dist_str.append(f"{i}-{i + 9}:{c}")
stats = {
'科目': subj,
'最高分': s_data.max(),
'最低分': s_data.min(),
'平均分': round(s_data.mean(), 1),
'优秀率': f"{excellent / count:.1%}",
'及格率': f"{passing / count:.1%}",
'分数分布': " | ".join(dist_str),
'_std': s_data.std() # 内部使用,导出时删除
}
subject_stats_list.append(stats)
sub_df = pd.DataFrame(subject_stats_list)
# 导出 CSV 2
sub_df.drop(columns=['_std']).to_csv('2_科目统计分析表.csv', index=False, encoding='utf-8-sig')
# ========================
# 3. 生成每个学生的图片
# ========================
# 准备科目平均分字典和标准差字典,用于绘图对比
sub_avg_map = dict(zip(sub_df['科目'], sub_df['平均分']))
sub_std_map = dict(zip(sub_df['科目'], sub_df['_std']))
for student_id, row in df.iterrows():
# 获取该生统计信息
info = student_stats.loc[student_id]
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 12))
fig.suptitle(f'学生成绩报告: {student_id} (第{info["排名"]}名)', fontsize=16, fontweight='bold')
# A. 柱状图
scores = [row[s] for s in SUBJECTS]
avgs = [sub_avg_map[s] for s in SUBJECTS]
x = np.arange(len(SUBJECTS))
width = 0.35
r1 = ax1.bar(x - width / 2, scores, width, label='个人得分', color='#4CAF50')
r2 = ax1.bar(x + width / 2, avgs, width, label='班级平均', color='#2196F3', alpha=0.6)
ax1.set_ylabel('分数')
ax1.set_title('各科得分与班级平均分对比')
ax1.set_xticks(x)
ax1.set_xticklabels(SUBJECTS)
ax1.legend()
ax1.bar_label(r1, padding=3)
ax1.bar_label(r2, padding=3, fmt='%.1f')
# B. 异常检测与文本
anomalies = []
for s in SUBJECTS:
mu = sub_avg_map[s]
sigma = sub_std_map[s]
val = row[s]
if sigma > 0: # 避免除0
if val > mu + 2 * sigma:
anomalies.append(f"{s}: {val}分 (显著高于平均 {mu},表现极其优异)")
elif val < mu - 2 * sigma:
anomalies.append(f"{s}: {val}分 (显著低于平均 {mu},需重点帮扶)")
text_c = f"--- 综合评价 ---\n"
text_c += f"总分: {info['总分']}\n"
text_c += f"平均分: {info['平均分']}\n"
text_c += f"发挥稳定性(标准差): {info['成绩波动(标准差)']} (越低越稳)\n"
text_c += f"最好科目: {info['最好科目']}\n"
text_c += f"最差科目: {info['最差科目']}\n\n"
text_c += f"--- 异常预警 (超出平均分±2个标准差) ---\n"
if anomalies:
text_c += "\n".join(anomalies)
else:
text_c += "各科成绩均在正常波动范围内。"
ax2.text(0.05, 0.95, text_c, transform=ax2.transAxes, fontsize=12, verticalalignment='top',
linespacing=1.8)
ax2.axis('off')
output_path = os.path.join(report_dir, f"{student_id}_分析报告.png")
plt.tight_layout()
plt.savefig(output_path)
plt.close(fig)
messagebox.showinfo("成功", f"分析完成!\n\n已生成表格文件和 {len(df)} 张学生报告图片。\n请查看程序所在目录。")
except Exception as e:
messagebox.showerror("运行错误", f"分析过程中发生错误:\n{str(e)}")
if __name__ == "__main__":
root = tk.Tk()
app = GradeSystemApp(root)
root.mainloop()
+193
View File
@@ -0,0 +1,193 @@
import numpy as np
import tkinter as tk
from tkinter import ttk, messagebox
class MatrixGenerator:
def __init__(self, n, step, start):
self.n = n
self.step = step
self.start = start
self.matrix = np.zeros((n, n), dtype=int)
# 预先生成等差数列
self.values = [start + i * step for i in range(n * n)]
def generate_spiral(self, clockwise=True):
m = self.matrix
n = self.n
visited = [[False] * n for _ in range(n)]
# 顺时针方向:右 -> 下 -> 左 -> 上
if clockwise:
dr = [0, 1, 0, -1]
dc = [1, 0, -1, 0]
# 逆时针方向:下 -> 右 -> 上 -> 左
else:
dr = [1, 0, -1, 0]
dc = [0, 1, 0, -1]
r, c, di = 0, 0, 0
for i in range(n * n):
m[r][c] = self.values[i]
visited[r][c] = True
# 尝试下一个位置
nr, nc = r + dr[di], c + dc[di]
# 检查边界及是否已访问
if 0 <= nr < n and 0 <= nc < n and not visited[nr][nc]:
r, c = nr, nc
else:
# 转向
di = (di + 1) % 4
r, c = r + dr[di], c + dc[di]
return m
def generate_zigzag(self):
m = self.matrix
idx = 0
for i in range(2 * self.n - 1):
if i < self.n:
r, c = 0, i
else:
r, c = i - self.n + 1, self.n - 1
curr_diag = []
while r < self.n and c >= 0:
curr_diag.append((r, c))
r += 1;
c -= 1
if i % 2 == 0: curr_diag.reverse()
for r, c in curr_diag:
m[r][c] = self.values[idx];
idx += 1
return m
def generate_snake(self):
m = self.matrix
idx = 0
for i in range(self.n):
# 偶数行从左往右,奇数行从右往左
cols = range(self.n) if i % 2 == 0 else range(self.n - 1, -1, -1)
for j in cols:
m[i][j] = self.values[idx];
idx += 1
return m
def generate_diagonal(self):
m = self.matrix
idx = 0
for d in range(2 * self.n - 1):
for i in range(max(0, d - self.n + 1), min(d + 1, self.n)):
m[i][d - i] = self.values[idx];
idx += 1
return m
class MatrixApp:
def __init__(self, root):
self.root = root
self.root.title("多功能矩阵生成器 Pro")
self.root.geometry("700x700")
# 输入面板
input_frame = ttk.LabelFrame(root, text="矩阵参数配置", padding=10)
input_frame.pack(fill="x", padx=15, pady=10)
# 使用 Grid 布局排列输入框
ttk.Label(input_frame, text="矩阵维数 (n):").grid(row=0, column=0, sticky="w", padx=5)
self.n_entry = ttk.Entry(input_frame, width=15);
self.n_entry.insert(0, "6")
self.n_entry.grid(row=0, column=1, pady=5)
ttk.Label(input_frame, text="起始数值:").grid(row=0, column=2, sticky="w", padx=5)
self.start_entry = ttk.Entry(input_frame, width=15);
self.start_entry.insert(0, "1")
self.start_entry.grid(row=0, column=3, pady=5)
ttk.Label(input_frame, text="数值步长:").grid(row=1, column=0, sticky="w", padx=5)
self.step_entry = ttk.Entry(input_frame, width=15);
self.step_entry.insert(0, "1")
self.step_entry.grid(row=1, column=1, pady=5)
ttk.Label(input_frame, text="填充模式:").grid(row=1, column=2, sticky="w", padx=5)
self.mode_cb = ttk.Combobox(input_frame, width=13, values=["螺旋形", "之字形", "对角线", "蛇形"],
state="readonly")
self.mode_cb.current(0);
self.mode_cb.grid(row=1, column=3, pady=5)
ttk.Label(input_frame, text="旋转方向:").grid(row=2, column=0, sticky="w", padx=5)
self.dir_cb = ttk.Combobox(input_frame, width=13, values=["顺时针 (Clockwise)", "逆时针 (Counterclockwise)"],
state="readonly")
self.dir_cb.current(0);
self.dir_cb.grid(row=2, column=1, pady=5)
# 按钮
btn = ttk.Button(input_frame, text="立即生成矩形", command=self.handle_generate)
btn.grid(row=2, column=2, columnspan=2, sticky="nsew", padx=5, pady=5)
# 输出区域
self.text_area = tk.Text(root, font=("Consolas", 11), wrap="none", bg="#f8f9fa")
self.text_area.pack(fill="both", expand=True, padx=15, pady=10)
# 滚动条控制
sy = ttk.Scrollbar(self.text_area, orient="vertical", command=self.text_area.yview)
sy.pack(side="right", fill="y")
sx = ttk.Scrollbar(self.text_area, orient="horizontal", command=self.text_area.xview)
sx.pack(side="bottom", fill="x")
self.text_area.configure(yscrollcommand=sy.set, xscrollcommand=sx.set)
def handle_generate(self):
try:
# 验证输入
n_str = self.n_entry.get()
if not n_str.isdigit(): raise ValueError("矩阵大小必须是正整数")
n = int(n_str)
if n <= 0: raise ValueError("矩阵大小必须大于0")
if n > 60: raise ValueError("大小超过60可能导致显示错乱")
start = int(self.start_entry.get())
step = int(self.step_entry.get())
gen = MatrixGenerator(n, step, start)
mode = self.mode_cb.get()
direction = "顺时针" in self.dir_cb.get()
if mode == "螺旋形":
res = gen.generate_spiral(clockwise=direction)
elif mode == "之字形":
res = gen.generate_zigzag()
elif mode == "对角线":
res = gen.generate_diagonal()
else:
res = gen.generate_snake()
self.display_matrix(res)
except ValueError as e:
messagebox.showerror("参数错误", str(e))
except Exception as e:
messagebox.showerror("运行时错误", f"发生未知错误: {e}")
def display_matrix(self, matrix):
self.text_area.delete(1.0, tk.END)
# 获取矩阵中绝对值最大的数字来确定列宽
max_val = np.abs(matrix).max()
col_width = len(str(max_val)) + 2
if col_width < 4: col_width = 4
output = []
for row in matrix:
line = "".join(f"{item:>{col_width}}" for item in row)
output.append(line)
self.text_area.insert(tk.END, "\n".join(output))
if __name__ == "__main__":
root = tk.Tk()
# 设置简单的样式
style = ttk.Style()
style.theme_use('clam')
app = MatrixApp(root)
root.mainloop()
-5
View File
@@ -1,5 +0,0 @@
## 全自动艺术评分系统 - AutoArtRatingSystem.py
```shell
pip install numpy pyinstaller
pyinstaller -F --windowed --hidden-import=tkinter --hidden-import=numpy "AutoArtRatingSystem.py"
```