高中成绩可视化平台开发笔记
高中成绩可视化平台(1)
一、项目概述
本系统是一个基于 PyQt5 和 Matplotlib 的高中成绩数据可视化分析平台,旨在帮助教师快速了解学生成绩分布、班级对比、学科表现等关键指标。平台支持文科与理科的数据切换,并提供多个维度的图表展示和交互式操作。
核心功能:
- 文科/理科数据动态切换
- 四个核心分析页面(总览、学科分析、班级分析、排名分析)
- 图表联动刷新机制
- 表格与图表双向绑定
- 自定义样式与视觉美化
二、技术选型
技术 | 用途 |
---|---|
PyQt5 | GUI 界面构建 |
Pandas | 数据处理与分析 |
Matplotlib | 图表绘制 |
QTabWidget | 多选项卡管理 |
QComboBox / QTableWidget | 控件交互 |
三、模块划分与类结构
整个平台主要由两个类组成:
ScoreVisualizationPlatform
:主窗口类,负责 UI 构建与事件处理DataProcessor
:数据处理类,封装所有数据读取与分析逻辑
四、UI 构建与控件初始化
def __init__(self):super().__init__()self.setWindowTitle("2023级成绩可视化平台")self.resize(1200, 800)# 初始化数据处理器self.data_processor = DataProcessor()# 主布局main_layout = QVBoxLayout(self)control_panel = QWidget()control_layout = QHBoxLayout(control_panel)# 下拉框选择文理类型self.stream_combo = QComboBox()self.stream_combo.addItems(["文科", "理科"])control_layout.addWidget(QLabel("文理类型:"))control_layout.addWidget(self.stream_combo)# 科目选择下拉框self.subject_combo = QComboBox()control_layout.addWidget(QLabel("科目选择:"))control_layout.addWidget(self.subject_combo)# 班级选择下拉框self.classes_combo = QComboBox()control_layout.addWidget(QLabel("班级选择:"))control_layout.addWidget(self.classes_combo)# 加载数据按钮load_button = QPushButton("加载数据")control_layout.addWidget(load_button)# 添加控件到主布局main_layout.addWidget(control_panel)# 创建选项卡self.tab_widget = QTabWidget()main_layout.addWidget(self.tab_widget)# 初始化各选项卡self.create_overview_tab()self.create_subject_analysis_tab()self.create_class_analysis_tab()self.create_ranking_tab()# 绑定信号self.stream_combo.currentTextChanged.connect(self.on_data_type_changed)self.subject_combo.currentTextChanged.connect(lambda: self.refresh_all_charts())self.classes_combo.currentTextChanged.connect(lambda: self.refresh_all_charts())load_button.clicked.connect(self.load_data)
📌 提示:该部分完成主窗口的创建,包含控制面板、四个选项卡以及数据加载按钮。
五、选项卡页面设计与实现
1. 总览页 create_overview_tab()
def create_overview_tab(self):tab = QWidget()self.tab_widget.addTab(tab, "总览")layout = QGridLayout(tab)# 图1:总分前20名图表self.total_score_chart = ChartWidget("总分Top20")layout.addWidget(self.total_score_chart, 0, 0, 1, 1)# 图2:班级占比图表self.class_distribution_chart = ChartWidget("Top20班级占比")layout.addWidget(self.class_distribution_chart, 0, 3, 1, 3)# 图3:各科目平均分对比self.subject_avg_chart = ChartWidget("学科Top20")layout.addWidget(self.subject_avg_chart, 1, 0, 1, 1)# 图4:班级学科分布(占第1行后两列)self.class_subject_chart = ChartWidget("学科Top20班级占比")layout.addWidget(self.class_subject_chart, 1, 3, 1, 3)
图表说明:
区域 | 内容 |
---|---|
左上 | 总分前20名柱状图 |
右上 | 班级分布饼图 |
左下 | 当前科目前20名柱状图 |
右下 | 当前科目班级分布饼图 |
2. 学科分析页 create_subject_analysis_tab()
def create_subject_analysis_tab(self):tab = QWidget()self.tab_widget.addTab(tab, "学科分析")layout = QGridLayout(tab)self.passing_rank = ChartWidget("本科上线排名")layout.addWidget(self.passing_rank, 0, 0)self.subject_stats_chart = ChartWidget("各科目统计分析")layout.addWidget(self.subject_stats_chart, 0, 1)self.single_subject_chart = ChartWidget("单科目上线人数排名")layout.addWidget(self.single_subject_chart, 1, 0)self.correlation_chart = ChartWidget("科目成绩相关性分析")layout.addWidget(self.correlation_chart, 1, 1)
图表说明:
区域 | 内容 |
---|---|
左上 | 各班过线人数柱状图 |
右上 | 各科平均分柱状图 |
左下 | 各科及格人数柱状图 |
右下 | 两个科目的散点图(显示相关性) |
3. 班级分析页 create_class_analysis_tab()
def create_class_analysis_tab(self):tab = QWidget()self.tab_widget.addTab(tab, "班级分析")layout = QGridLayout(tab)self.class_avg_chart = ChartWidget("各班级平均分对比")layout.addWidget(self.class_avg_chart, 0, 0)self.class_score_dist_chart = ChartWidget("班级成绩分布")layout.addWidget(self.class_score_dist_chart, 0, 1)self.class_subject_performance_chart = ChartWidget("班级各科表现")layout.addWidget(self.class_subject_performance_chart, 1, 0)self.total_top_5 = ChartWidget("各班级top5各科表现")layout.addWidget(self.total_top_5, 1, 1)
图表说明:
区域 | 内容 |
---|---|
左上 | 班级平均分柱状图 |
右上 | 成绩分布直方图 |
左下 | 各科平均分折线图 |
右下 | 每个班级 top5 学生的各科成绩雷达图 |
4. 排名分析页 create_ranking_tab()
def create_ranking_tab(self):tab = QWidget()self.tab_widget.addTab(tab, "排名分析")main_layout = QVBoxLayout(tab)tables_container = QWidget()tables_layout = QVBoxLayout(tables_container)inner_layout = QHBoxLayout(tab)ranking_group = QVBoxLayout()self.ranking_title = QLabel("年级前100名学生")self.ranking_table = QTableWidget()self.ranking_table.setSortingEnabled(True)ranking_group.addWidget(self.ranking_title)ranking_group.addWidget(self.ranking_table)class_group = QVBoxLayout()self.class_title = QLabel("当前班级单科成绩排名")self.class_tables = QTableWidget()self.class_tables.setSortingEnabled(True)class_group.addWidget(self.class_title)class_group.addWidget(self.class_tables)inner_layout.addLayout(ranking_group, stretch=1)inner_layout.addLayout(class_group, stretch=1)tables_layout.addLayout(inner_layout)main_layout.addWidget(tables_container)self.figure = Figure(figsize=(5, 3))self.canvas = FigureCanvas(self.figure)self.canvas.setStyleSheet("background-color:rgba(0, 1, 1, 0.3); border: 1px solid #ccc;")main_layout.addWidget(self.canvas)
图表说明:
区域 | 内容 |
---|---|
上部 | 两个表格(年级前100名 / 当前班级单科排名) |
下部 | 动态绘图区域(用于展示趋势、对比等图表) |
六、数据处理与图表联动
1. 数据加载与刷新机制
def load_data(self):if self.data_processor.load_data():self.refresh_all_charts()QMessageBox.information(self, "成功", "数据加载完成!")else:QMessageBox.warning(self, "错误", "数据加载失败,请检查数据文件!")def on_data_type_changed(self, data_type):self.refresh_all_charts()def refresh_all_charts(self):data_type = "liberal" if self.stream_combo.currentText() == "文科" else "science"subject_prefix = "文科" if data_type == "liberal" else "理科"subject_type = self.subject_combo.currentText()self.total_score_chart.title = f"2023级{subject_prefix}总分前20名"self.class_distribution_chart.title = f"2023级{subject_prefix}前20名班级占比"self.update_overview_charts(data_type, subject_type)self.update_subject_analysis_charts(data_type)self.update_class_analysis_charts(data_type)self.update_ranking_table(data_type)
✅ 特点:通过组合文理科类型 + 科目 + 班级,动态更新所有图表与表格内容。
2. 总览页图表更新 update_overview_charts()
def update_overview_charts(self, data_type, subject):# 总分前20名柱状图top_students = self.data_processor.get_top_students(data_type, 20)if top_students is not None:self.total_score_chart.plot_bar_chart(top_students, '姓名', '总分',f"{'文科' if data_type == 'liberal' else '理科'}总分Top20")# 班级分布饼图top20_class_dist = top_students['班级'].value_counts().reset_index()top20_class_dist.columns = ['班级', '人数']self.class_distribution_chart.plot_pie_chart(top20_class_dist, '班级', '人数',f"{'文科' if data_type == 'liberal' else '理科'}Top20班级占比")# 单科前20名柱状图subject_top20 = data.nlargest(20, subject)[['姓名', subject]]self.subject_avg_chart.plot_bar_chart(subject_top20, '姓名', subject,f"{'文科' if data_type == 'liberal' else '理科'}{subject}Top20")# 班级学科分布饼图class_subject_data = self.data_processor.get_class_subject_top20(data_type)subject_class_dist = class_subject_data[subject].reset_index()subject_class_dist.columns = ['班级', '人数']self.class_subject_chart.plot_pie_chart(subject_class_dist, '班级', '人数',f"{'文科' if data_type == 'liberal' else '理科'}{subject}Top20班级占比")
3. 学科分析页图表更新 update_subject_analysis_charts()
def update_subject_analysis_charts(self, data_type):passing = self.data_processor.get_pass_line(data_type)totals = self.data_processor.calculate_total_scores(data_type)ranks = totals[totals['总分'] > passing].groupby('班级').size(). \reset_index(name='人数').sort_values(by='人数', ascending=False)self.passing_rank.plot_bar_chart(ranks, '班级', '人数', f"{'文科' if data_type == 'liberal' else '理科'}各班过线人数")subject_analysis = self.data_processor.get_subject_analysis(data_type)avg_scores = subject_analysis['平均分'].reset_index()avg_scores.columns = ['科目', '平均分']self.subject_stats_chart.plot_bar_chart(avg_scores, '科目', '平均分', "各科目平均分对比")online_counts = []for subject in subjects:if subject in data.columns:online_count = (data[subject] >= 60).sum()online_counts.append({'科目': subject, '及格人数': online_count})online_df = pd.DataFrame(online_counts)self.single_subject_chart.plot_bar_chart(online_df, '科目', '及格人数', "各科目及格人数统计")subject1, subject2 = subjects[0], subjects[1]clean_data = data[[subject1, subject2]].dropna()ax.scatter(clean_data[subject1], clean_data[subject2], alpha=0.9, edgecolors='#8A0808')self.correlation_chart.figure.tight_layout()self.correlation_chart.canvas.draw()
4. 班级分析页图表更新 update_class_analysis_charts()
def update_class_analysis_charts(self, data_type):# 平均总分柱状图class_avg_scores = []for class_name in data['班级'].unique():class_data = data[data['班级'] == class_name]total_scores = class_data[subjects].sum(axis=1, skipna=True)avg_score = total_scores.mean()class_avg_scores.append({'班级': class_name, '平均总分': avg_score})class_avg_df = pd.DataFrame(class_avg_scores).sort_values('平均总分', ascending=False)self.class_avg_chart.plot_bar_chart(class_avg_df, '班级', '平均总分', "各班级平均总分对比")# 分数段分布柱状图bins = [0, 300, 400, 500, 600, 700, 800]labels = ['0-300', '300-400', '400-500', '500-600', '600-700', '700-800']score_dist = []for label, (low, high) in zip(labels, zip(bins[:-1], bins[1:])):count = ((total_scores_data['总分'] >= low) & (total_scores_data['总分'] < high)).sum()score_dist.append({'分数段': label, '人数': count})score_dist_df = pd.DataFrame(score_dist)self.class_score_dist_chart.plot_bar_chart(score_dist_df, '分数段', '人数', "总分分布统计")# 各科表现堆叠柱状图stacked_data = []for class_name in sorted(all_classes):row = {'班级': class_name}for subject, subject_data in class_subject_data.items():row[subject] = subject_data.get(class_name, 0)stacked_data.append(row)stacked_df = pd.DataFrame(stacked_data)self.class_subject_performance_chart.plot_stacked_bar(stacked_df, "各班级各科目前20名人数分布")# 各班前5名图表top_5 = self.data_processor.get_class_top_5(data_type, class_name)[0][['姓名'] + subjects]self.total_top_5.plot_stacked_bar(data=top_5, title=f"{class_name} 学生学科成绩分布",item_1='姓名', item_2='姓名',x_label='学生姓名', y_label='分数')
5. 排名分析页表格与图表更新 update_ranking_table()
def update_ranking_table(self, data_type):top_students = self.data_processor.get_top_students(data_type, 100)if top_students is not None:self.ranking_table.setRowCount(len(top_students))self.ranking_table.setColumnCount(4)self.ranking_table.setHorizontalHeaderLabels(['排名', '姓名', '班级', '总分'])for i, (_, row) in enumerate(top_students.iterrows()):self.ranking_table.setItem(i, 0, QTableWidgetItem(str(i + 1)))self.ranking_table.setItem(i, 1, QTableWidgetItem(str(row['姓名'])))self.ranking_table.setItem(i, 2, QTableWidgetItem(str(row['班级'])))self.ranking_table.setItem(i, 3, QTableWidgetItem(f"{row['总分']:.1f}"))self.ranking_table.resizeColumnsToContents()cla = self.classes_combo.currentText()sujects = self.subject_combo.currentText()data = self.data_processor.get_subject_scores(data_type, cla, sujects)if data is not None:self.class_tables.setRowCount(len(data))self.class_tables.setColumnCount(4)self.class_tables.setHorizontalHeaderLabels(['单科排名', '姓名', '班级', sujects])for i, (_, row) in enumerate(data.iterrows()):self.class_tables.setItem(i, 0, QTableWidgetItem(str(i + 1)))self.class_tables.setItem(i, 1, QTableWidgetItem(str(row['姓名'])))self.class_tables.setItem(i, 2, QTableWidgetItem(str(row['班级'])))self.class_tables.setItem(i, 3, QTableWidgetItem(f"{row[sujects]:.1f}"))self.class_tables.resizeColumnsToContents()data = data.head()self.figure.clear()self.figure.patch.set_alpha(0.0)ax = self.figure.add_subplot(111)ax.set_facecolor((0, 1, 1, 0.3))bars = ax.bar(data["姓名"], data[sujects], color="#4CAF50")for bar in bars:yval = bar.get_height()ax.text(bar.get_x() + bar.get_width() / 2.0, yval, int(yval),va='bottom', ha='center', color='cyan')ax.set_title(f"{'文科' if data_type == 'liberal' else '理科'}-{cla}-{sujects}前5名", color='cyan')ax.set_ylabel('分数', color='cyan')ax.set_xlabel('姓名', color='cyan')ax.grid(True, linestyle='--', alpha=0.6)ax.tick_params(axis='x', colors='cyan')ax.tick_params(axis='y', colors='cyan')self.canvas.draw()
高中成绩可视化平台(2)
一、项目概述
本系统是一个基于 PyQt5 和 Matplotlib 的高中成绩数据可视化分析平台,旨在帮助教师快速了解学生成绩分布、班级对比、学科表现等关键指标。平台支持文科与理科的数据切换,并提供多个维度的图表展示和交互式操作。
核心功能:
- 文科/理科数据动态切换
- 四个核心分析页面(总览、学科分析、班级分析、排名分析)
- 图表联动刷新机制
- 表格与图表双向绑定
- 自定义样式与视觉美化
二、技术选型
技术 | 用途 |
---|---|
PyQt5 | GUI 界面构建 |
Pandas | 数据处理与分析 |
Matplotlib | 图表绘制 |
QTabWidget | 多选项卡管理 |
QComboBox / QTableWidget | 控件交互 |
三、模块划分与类结构
整个平台主要由两个类组成:
ScoreVisualizationPlatform
:主窗口类,负责 UI 构建与事件处理DataProcessor
:数据处理类,封装所有数据读取与分析逻辑
四、UI 构建与控件初始化
def __init__(self):super().__init__()self.setWindowTitle("2023级成绩可视化平台")self.resize(1200, 800)# 初始化数据处理器self.data_processor = DataProcessor()# 主布局main_layout = QVBoxLayout(self)control_panel = QWidget()control_layout = QHBoxLayout(control_panel)# 下拉框选择文理类型self.stream_combo = QComboBox()self.stream_combo.addItems(["文科", "理科"])control_layout.addWidget(QLabel("文理类型:"))control_layout.addWidget(self.stream_combo)# 科目选择下拉框self.subject_combo = QComboBox()control_layout.addWidget(QLabel("科目选择:"))control_layout.addWidget(self.subject_combo)# 班级选择下拉框self.classes_combo = QComboBox()control_layout.addWidget(QLabel("班级选择:"))control_layout.addWidget(self.classes_combo)# 加载数据按钮load_button = QPushButton("加载数据")control_layout.addWidget(load_button)# 添加控件到主布局main_layout.addWidget(control_panel)# 创建选项卡self.tab_widget = QTabWidget()main_layout.addWidget(self.tab_widget)# 初始化各选项卡self.create_overview_tab()self.create_subject_analysis_tab()self.create_class_analysis_tab()self.create_ranking_tab()# 绑定信号self.stream_combo.currentTextChanged.connect(self.on_data_type_changed)self.subject_combo.currentTextChanged.connect(lambda: self.refresh_all_charts())self.classes_combo.currentTextChanged.connect(lambda: self.refresh_all_charts())load_button.clicked.connect(self.load_data)
📌 提示:该部分完成主窗口的创建,包含控制面板、四个选项卡以及数据加载按钮。
五、选项卡页面设计与实现
1. 总览页 create_overview_tab()
def create_overview_tab(self):tab = QWidget()self.tab_widget.addTab(tab, "总览")layout = QGridLayout(tab)# 图1:总分前20名图表self.total_score_chart = ChartWidget("总分Top20")layout.addWidget(self.total_score_chart, 0, 0, 1, 1)# 图2:班级占比图表self.class_distribution_chart = ChartWidget("Top20班级占比")layout.addWidget(self.class_distribution_chart, 0, 3, 1, 3)# 图3:各科目平均分对比self.subject_avg_chart = ChartWidget("学科Top20")layout.addWidget(self.subject_avg_chart, 1, 0, 1, 1)# 图4:班级学科分布(占第1行后两列)self.class_subject_chart = ChartWidget("学科Top20班级占比")layout.addWidget(self.class_subject_chart, 1, 3, 1, 3)
图表说明:
区域 | 内容 |
---|---|
左上 | 总分前20名柱状图 |
右上 | 班级分布饼图 |
左下 | 当前科目前20名柱状图 |
右下 | 当前科目班级分布饼图 |
2. 学科分析页 create_subject_analysis_tab()
def create_subject_analysis_tab(self):tab = QWidget()self.tab_widget.addTab(tab, "学科分析")layout = QGridLayout(tab)self.passing_rank = ChartWidget("本科上线排名")layout.addWidget(self.passing_rank, 0, 0)self.subject_stats_chart = ChartWidget("各科目统计分析")layout.addWidget(self.subject_stats_chart, 0, 1)self.single_subject_chart = ChartWidget("单科目上线人数排名")layout.addWidget(self.single_subject_chart, 1, 0)self.correlation_chart = ChartWidget("科目成绩相关性分析")layout.addWidget(self.correlation_chart, 1, 1)
图表说明:
区域 | 内容 |
---|---|
左上 | 各班过线人数柱状图 |
右上 | 各科平均分柱状图 |
左下 | 各科及格人数柱状图 |
右下 | 两个科目的散点图(显示相关性) |
3. 班级分析页 create_class_analysis_tab()
def create_class_analysis_tab(self):tab = QWidget()self.tab_widget.addTab(tab, "班级分析")layout = QGridLayout(tab)self.class_avg_chart = ChartWidget("各班级平均分对比")layout.addWidget(self.class_avg_chart, 0, 0)self.class_score_dist_chart = ChartWidget("班级成绩分布")layout.addWidget(self.class_score_dist_chart, 0, 1)self.class_subject_performance_chart = ChartWidget("班级各科表现")layout.addWidget(self.class_subject_performance_chart, 1, 0)self.total_top_5 = ChartWidget("各班级top5各科表现")layout.addWidget(self.total_top_5, 1, 1)
图表说明:
区域 | 内容 |
---|---|
左上 | 班级平均分柱状图 |
右上 | 成绩分布直方图 |
左下 | 各科平均分折线图 |
右下 | 每个班级 top5 学生的各科成绩雷达图 |
4. 排名分析页 create_ranking_tab()
def create_ranking_tab(self):tab = QWidget()self.tab_widget.addTab(tab, "排名分析")main_layout = QVBoxLayout(tab)tables_container = QWidget()tables_layout = QVBoxLayout(tables_container)inner_layout = QHBoxLayout(tab)ranking_group = QVBoxLayout()self.ranking_title = QLabel("年级前100名学生")self.ranking_table = QTableWidget()self.ranking_table.setSortingEnabled(True)ranking_group.addWidget(self.ranking_title)ranking_group.addWidget(self.ranking_table)class_group = QVBoxLayout()self.class_title = QLabel("当前班级单科成绩排名")self.class_tables = QTableWidget()self.class_tables.setSortingEnabled(True)class_group.addWidget(self.class_title)class_group.addWidget(self.class_tables)inner_layout.addLayout(ranking_group, stretch=1)inner_layout.addLayout(class_group, stretch=1)tables_layout.addLayout(inner_layout)main_layout.addWidget(tables_container)self.figure = Figure(figsize=(5, 3))self.canvas = FigureCanvas(self.figure)self.canvas.setStyleSheet("background-color:rgba(0, 1, 1, 0.3); border: 1px solid #ccc;")main_layout.addWidget(self.canvas)
图表说明:
区域 | 内容 |
---|---|
上部 | 两个表格(年级前100名 / 当前班级单科排名) |
下部 | 动态绘图区域(用于展示趋势、对比等图表) |
六、数据处理与图表联动
1. 数据加载与刷新机制
def load_data(self):if self.data_processor.load_data():self.refresh_all_charts()QMessageBox.information(self, "成功", "数据加载完成!")else:QMessageBox.warning(self, "错误", "数据加载失败,请检查数据文件!")def on_data_type_changed(self, data_type):self.refresh_all_charts()def refresh_all_charts(self):data_type = "liberal" if self.stream_combo.currentText() == "文科" else "science"subject_prefix = "文科" if data_type == "liberal" else "理科"subject_type = self.subject_combo.currentText()self.total_score_chart.title = f"2023级{subject_prefix}总分前20名"self.class_distribution_chart.title = f"2023级{subject_prefix}前20名班级占比"self.update_overview_charts(data_type, subject_type)self.update_subject_analysis_charts(data_type)self.update_class_analysis_charts(data_type)self.update_ranking_table(data_type)
✅ 特点:通过组合文理科类型 + 科目 + 班级,动态更新所有图表与表格内容。
2. 总览页图表更新 update_overview_charts()
def update_overview_charts(self, data_type, subject):# 总分前20名柱状图top_students = self.data_processor.get_top_students(data_type, 20)if top_students is not None:self.total_score_chart.plot_bar_chart(top_students, '姓名', '总分',f"{'文科' if data_type == 'liberal' else '理科'}总分Top20")# 班级分布饼图top20_class_dist = top_students['班级'].value_counts().reset_index()top20_class_dist.columns = ['班级', '人数']self.class_distribution_chart.plot_pie_chart(top20_class_dist, '班级', '人数',f"{'文科' if data_type == 'liberal' else '理科'}Top20班级占比")# 单科前20名柱状图subject_top20 = data.nlargest(20, subject)[['姓名', subject]]self.subject_avg_chart.plot_bar_chart(subject_top20, '姓名', subject,f"{'文科' if data_type == 'liberal' else '理科'}{subject}Top20")# 班级学科分布饼图class_subject_data = self.data_processor.get_class_subject_top20(data_type)subject_class_dist = class_subject_data[subject].reset_index()subject_class_dist.columns = ['班级', '人数']self.class_subject_chart.plot_pie_chart(subject_class_dist, '班级', '人数',f"{'文科' if data_type == 'liberal' else '理科'}{subject}Top20班级占比")
3. 学科分析页图表更新 update_subject_analysis_charts()
def update_subject_analysis_charts(self, data_type):passing = self.data_processor.get_pass_line(data_type)totals = self.data_processor.calculate_total_scores(data_type)ranks = totals[totals['总分'] > passing].groupby('班级').size(). \reset_index(name='人数').sort_values(by='人数', ascending=False)self.passing_rank.plot_bar_chart(ranks, '班级', '人数', f"{'文科' if data_type == 'liberal' else '理科'}各班过线人数")subject_analysis = self.data_processor.get_subject_analysis(data_type)avg_scores = subject_analysis['平均分'].reset_index()avg_scores.columns = ['科目', '平均分']self.subject_stats_chart.plot_bar_chart(avg_scores, '科目', '平均分', "各科目平均分对比")online_counts = []for subject in subjects:if subject in data.columns:online_count = (data[subject] >= 60).sum()online_counts.append({'科目': subject, '及格人数': online_count})online_df = pd.DataFrame(online_counts)self.single_subject_chart.plot_bar_chart(online_df, '科目', '及格人数', "各科目及格人数统计")subject1, subject2 = subjects[0], subjects[1]clean_data = data[[subject1, subject2]].dropna()ax.scatter(clean_data[subject1], clean_data[subject2], alpha=0.9, edgecolors='#8A0808')self.correlation_chart.figure.tight_layout()self.correlation_chart.canvas.draw()
4. 班级分析页图表更新 update_class_analysis_charts()
def update_class_analysis_charts(self, data_type):# 平均总分柱状图class_avg_scores = []for class_name in data['班级'].unique():class_data = data[data['班级'] == class_name]total_scores = class_data[subjects].sum(axis=1, skipna=True)avg_score = total_scores.mean()class_avg_scores.append({'班级': class_name, '平均总分': avg_score})class_avg_df = pd.DataFrame(class_avg_scores).sort_values('平均总分', ascending=False)self.class_avg_chart.plot_bar_chart(class_avg_df, '班级', '平均总分', "各班级平均总分对比")# 分数段分布柱状图bins = [0, 300, 400, 500, 600, 700, 800]labels = ['0-300', '300-400', '400-500', '500-600', '600-700', '700-800']score_dist = []for label, (low, high) in zip(labels, zip(bins[:-1], bins[1:])):count = ((total_scores_data['总分'] >= low) & (total_scores_data['总分'] < high)).sum()score_dist.append({'分数段': label, '人数': count})score_dist_df = pd.DataFrame(score_dist)self.class_score_dist_chart.plot_bar_chart(score_dist_df, '分数段', '人数', "总分分布统计")# 各科表现堆叠柱状图stacked_data = []for class_name in sorted(all_classes):row = {'班级': class_name}for subject, subject_data in class_subject_data.items():row[subject] = subject_data.get(class_name, 0)stacked_data.append(row)stacked_df = pd.DataFrame(stacked_data)self.class_subject_performance_chart.plot_stacked_bar(stacked_df, "各班级各科目前20名人数分布")# 各班前5名图表top_5 = self.data_processor.get_class_top_5(data_type, class_name)[0][['姓名'] + subjects]self.total_top_5.plot_stacked_bar(data=top_5, title=f"{class_name} 学生学科成绩分布",item_1='姓名', item_2='姓名',x_label='学生姓名', y_label='分数')
5. 排名分析页表格与图表更新 update_ranking_table()
def update_ranking_table(self, data_type):top_students = self.data_processor.get_top_students(data_type, 100)if top_students is not None:self.ranking_table.setRowCount(len(top_students))self.ranking_table.setColumnCount(4)self.ranking_table.setHorizontalHeaderLabels(['排名', '姓名', '班级', '总分'])for i, (_, row) in enumerate(top_students.iterrows()):self.ranking_table.setItem(i, 0, QTableWidgetItem(str(i + 1)))self.ranking_table.setItem(i, 1, QTableWidgetItem(str(row['姓名'])))self.ranking_table.setItem(i, 2, QTableWidgetItem(str(row['班级'])))self.ranking_table.setItem(i, 3, QTableWidgetItem(f"{row['总分']:.1f}"))self.ranking_table.resizeColumnsToContents()cla = self.classes_combo.currentText()sujects = self.subject_combo.currentText()data = self.data_processor.get_subject_scores(data_type, cla, sujects)if data is not None:self.class_tables.setRowCount(len(data))self.class_tables.setColumnCount(4)self.class_tables.setHorizontalHeaderLabels(['单科排名', '姓名', '班级', sujects])for i, (_, row) in enumerate(data.iterrows()):self.class_tables.setItem(i, 0, QTableWidgetItem(str(i + 1)))self.class_tables.setItem(i, 1, QTableWidgetItem(str(row['姓名'])))self.class_tables.setItem(i, 2, QTableWidgetItem(str(row['班级'])))self.class_tables.setItem(i, 3, QTableWidgetItem(f"{row[sujects]:.1f}"))self.class_tables.resizeColumnsToContents()data = data.head()self.figure.clear()self.figure.patch.set_alpha(0.0)ax = self.figure.add_subplot(111)ax.set_facecolor((0, 1, 1, 0.3))bars = ax.bar(data["姓名"], data[sujects], color="#4CAF50")for bar in bars:yval = bar.get_height()ax.text(bar.get_x() + bar.get_width() / 2.0, yval, int(yval),va='bottom', ha='center', color='cyan')ax.set_title(f"{'文科' if data_type == 'liberal' else '理科'}-{cla}-{sujects}前5名", color='cyan')ax.set_ylabel('分数', color='cyan')ax.set_xlabel('姓名', color='cyan')ax.grid(True, linestyle='--', alpha=0.6)ax.tick_params(axis='x', colors='cyan')ax.tick_params(axis='y', colors='cyan')self.canvas.draw()
七、项目结果图(部分)
附:qss样式
QMainWindow {background-color: #16003a;
}
QWidget {background-color: #16003a;color: cyan;
}
QTabWidget::pane {border: 1px solid rgba(221, 221, 221, 0);background-color: #16003a;
}
QTabBar::tab {background-color: #16003a;padding: 8px 16px;margin-right: 2px;border-top-left-radius: 4px;border-top-right-radius: 4px;
}
QTabBar::tab:selected {background-color: #00385e;color: #ffffff;
}
QPushButton {background-color: rgba(31, 106, 152, 0.81);color: cyan;border: none;padding: 8px 16px;border-radius: 4px;font-weight: bold;
}
QPushButton:hover {background-color: #00560f;
}
QGroupBox {color: #4dffff;font-size: 13px;border: 2px solid #4dffff;border-radius: 5px;margin-top: 2px;padding-top: 2px;
}
QGroupBox::title {color: cyan;subcontrol-origin: margin;left: 5px;padding: 0 5px 0 5px;
}
QLabel {color: cyan;text-align: center;background-color: #16003a;font-size: 20px;font-weight: bold;
}
QLabel#titleLabel {padding: 5px;font-size: 40px;font-family: "Microsoft YaHei";text-align: center;
}
QComboBox {background-color: rgba(162, 88, 0, 0.7);color: cyan;font-size: 12px;border: 1px solid #ffffff;border-radius: 35px;padding: 0px 5px;
}
QComboBox QAbstractItemView::item:hover {background-color: #0B6121;
}
- 数据: 需要数据或遇到问题可联系QQ(1591236129)
相关文章:
高中成绩可视化平台开发笔记
高中成绩可视化平台(1) 一、项目概述 本系统是一个基于 PyQt5 和 Matplotlib 的高中成绩数据可视化分析平台,旨在帮助教师快速了解学生成绩分布、班级对比、学科表现等关键指标。平台支持文科与理科的数据切换,并提供多个维度的图…...
圆周期性显示和消失——瞬态实现(CAD c#二次开发、插件定制)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Timers; [assembly: CommandClass(typeof(IfoxDemo.Commands))] namespace IfoxDemo {// 新增的圆形闪烁命令实…...
Spark SQL to_json 函数介绍
目录 前言函数介绍参数说明示例 前言 在Apache Hive中,并没有内置的to_json函数。在Apache Spark SQL中确实有to_json函数,它可以用来将结构化数据(如结构化类型或MAP类型)转换为JSON字符串。这个功能对于需要将表格数据输出为JSON格式的场景…...
5个免费的硬盘分区工具,操作简单功能全
电脑用久了,系统盘空间告急、数据盘混乱无序,很多人想重新分区,却又担心太复杂或怕搞坏硬盘。其实,只要用对工具,分区操作其实一点都不难。更重要的是,有很多免费的分区软件,不仅好用࿰…...
uniapp事件onLoad区分大小写
区分大小写。不然会不起作用。onLoad方法中的功能均不会被执行。 除了功能逻辑要检查外。大小写是要认真检查的一部分...
Flutter Riverpod 使用详细解析
📚 Flutter 状态管理系列文章目录 Flutter 状态管理(setState、InheritedWidget、 Provider 、Riverpod、 BLoC / Cubit、 GetX 、MobX 、Redux) setState() 使用详解:原理及注意事项 InheritedWidget 组件使用及原理 Flutter 中 Provider 的使用、注…...
算法打卡 day4
4 . 高精度算法 性质:数组或者容器从低位往高位依次存储大整数,方便进位。 4.1 高精度加法 给定两个正整数(不含前导 0),计算它们的和。 输入格式 共两行,每行包含一个整数。 输出格式 共一行,…...
权威认证!华宇TAS应用中间件荣获CCRC“中间件产品安全认证”
近日,华宇TAS应用中间件顺利通过了中国网络安全审查认证和市场监管大数据中心(CCRC)的信息安全认证,获得了IT产品信息安全认证证书。此次获证,标志着华宇TAS应用中间件在安全性、可靠性及合规性等方面达到行业领先水平,可以为政企…...
【Linux网络编程】多路转接IO(二)epoll
目录 epoll初识 epoll的相关系统调用 epoll的工作原理 epoll的优点 epoll的工作方式 水平触发 Level Triggered 工作模式 边缘触发 Edge Triggered 工作模式 对比LT和ET 理解 ET 模式和非阻塞文件描述符 epoll的惊群问题 基于LT模式的epoll代码样例 epoll初识 按照man…...
flutter的包管理#资源管理#调试Flutter应用#Flutter异常捕获
2.5 包管理 2.5.1 简介 在软件开发中,很多时候有一些公共的库或 SDK 可能会被很多项目用到,因此,将这些代码单独抽到一个独立模块,然后哪个项目需要使用时再直接集成这个模块,便可大大提高开发效率。很多编程语言或开…...
Unity Netcode自定义数据传输——结构体及其序列化
在 Unity Netcode 中,要实现自定义数据的网络传输,确实需要两个关键部分: ✅ 两个必需组件: 数据结构定义 public struct PlayerState : INetworkSerializable {public int id; // 字段1:玩家IDpublic bool …...
Vue 3 高级编程技巧
Vue 3 高级编程技巧 1. 计算属性 (Computed Properties) 含义:计算属性在依赖变化时会自动更新。以下是一个示例,展示当 firstName 或 lastName 变化时,fullName 也会更新。 实例: <script setup> import { ref, comput…...
GraphQL注入 -- GPN CTF 2025 Real Christmas
part 1 服务器会每段时间禁用已注册的账号,此处存在漏洞 def deactivate_user_graphql(email):graphql_endpoint current_app.config["GRAPHQL_ENDPOINT"]query f"""mutation {{deactivateUser (user: {{email: "{email}"}}){{ success…...
python打卡day43
疏锦行 作业: kaggle找到一个图像数据集,用cnn网络进行训练并且用grad-cam做可视化 进阶:并拆分成多个文件 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms# 数据预处理 tra…...
ethers.js express vue2 定时任务每天凌晨2点监听合约地址数据同步到Mysql整理
下面是一个完整的 Ethers.js Express Vue2 MySQL 实现方案,用于: 💡每天凌晨 2 点监听某合约地址的 Transfer 事件,写入 MySQL 数据库,并展示每日 NFT 交易量图表(Vue2 ECharts) ✅ 后端部分…...
内网穿透和端口映射的区别在哪?局域网提供互联网访问方案对比选择详解
内网穿透和端口映射是两个经常被提及的概念,它们对于实现网络中的内外网通信起着关键作用。内网穿透和端口映射都能够有效地将本地局域网地址提供给互联网上外网访问,但二者之间存在着显著的区别。 内网穿透与端口映射的核心区别在于实现方式和依赖条件…...
机器学习---正则化、过拟合抑制与特征筛选
专栏:机器学习 个人主页:云端筑梦狮 注:上一篇机器学习还差一小节,日后坑必会填上 一.正则化 什么是正则化 / 如何进行正则化 其实机器学习中正则化(regularization)的外在形式非常简单,就是在模型的损失函数中加…...
优化 ArcPy 脚本性能
合理设置环境变量 优化环境变量配置 ArcPy 提供了许多环境变量,用于控制地理处理工具的行为。合理设置环境变量可以优化脚本的性能。例如,设置“workspace”环境变量可以指定默认的工作空间,避免在脚本中重复指定工作空间路径。 Python 复制…...
Robyn高性能Web框架系列06:使用WebSocket实现产品智能助理
使用WebSocket实现产品智能助理 WebSocket原理与应用场景Robyn的WebSocket基本使用1、创建WebSocket服务2、侦听WebSocket事件3、向客户端发送消息4、向客户端广播消息5、使用查询参数6、主动关闭连接 示例:简易的产品智能助理1、产品数据部分2、产品信息部分3、智能…...
UDP 缓冲区
UDP 有接收缓冲区,没有发送缓冲区 引申问题 1、为什么没有发送缓冲区? 直接引用原文 “因为 UDP 是不可靠的,它不必保存应用进程的数据拷贝,因此无需一个真正的发送缓冲区” 2、没有发送缓冲区的情况下,sendto 的数…...
物联网与低代码:Node-RED如何赋能工业智能化与纵横智控的创新实践
在数字化浪潮席卷全球的今天,物联网(IoT)已从概念走向现实,成为连接物理世界与数字世界的关键桥梁。它通过将日常物品、工业设备等“物”嵌入传感器、软件及其他技术,使其能够通过网络相互连接并交换数据,从…...
【甲方安全视角】开源的安全悖论
文章目录 安全的充分必要条件:从「符号化信任」到「验证驱动安全」构建与分发的不可信链条迭代与审计的节奏错位代码透明与攻击面的对等暴露对普通用户的建议选择可信项目与品牌始终通过官方渠道获取软件注意权限与环境安全对“签名请求”、“连接钱包”等敏感操作保…...
GEO生成式引擎优化发展迅猛:热点数智化传播是GEO最佳路径
在人工智能技术浪潮的推动下,GEO生成式引擎优化已跃升为行业技术演进与产业发展相融合的核心赛道。通过系统性梳理其发展脉络,我们可清晰勾勒出技术突破与产业变革交织的演进轨迹,其发展进程包含以下重要节点。 2023年4月,GPT-4发…...
【unity游戏开发——网络】计算机网络中的三种数据管理模型(分散式、集中式、分布式)和三大通信模型(C/S、B/S、P2P)
注意:考虑到热更新的内容比较多,我将热更新的内容分开,并全部整合放在【unity游戏开发——网络】专栏里,感兴趣的小伙伴可以前往逐一查看学习。 文章目录 一、数据管理模型1、分散式 (Decentralized - 各管各的)2、集中式 (Centra…...
MR30分布式 IO在物流堆垛机的应用
在现代物流行业蓬勃发展的浪潮中,物流堆垛机作为自动化仓储系统的核心设备,承担着货物的高效存取与搬运任务。它凭借自动化操作、高精度定位等优势,极大地提升了仓储空间利用率和货物周转效率。然而,随着物流行业的高速发展&#…...
香港维尔利健康科技集团推出AI辅助医学影像训练平台,助力医护人才数字化转型
香港维尔利健康科技集团近日正式发布其自主研发的“AI辅助医学影像训练平台(V-MedTrain)”,这一创新平台的上线,标志着医学影像教育迈入智能化辅助教学新时代。依托人工智能与大数据分析技术,香港维尔利健康科技集团在…...
2025学年湖北省职业院校技能大赛 “信息安全管理与评估”赛项 样题卷(五)
2025学年湖北省职业院校技能大赛 “信息安全管理与评估”赛项 样题卷(五) 第二部分:网络安全事件响应、数字取证调查、应用程序安全任务书任务 1:应急响应(可以培训有答案)任务 2:通信数据分析取…...
基于 Python 的批量文件重命名软件设计与实现
在工作过程中,经常有很多文件,想要对文件名进行批量改名,特此写了一个程序,以实现此功能。 一、批量文件重命名软件设计原理 (一)核心原理阐述 批量文件重命名软件的核心原理在于运用操作系统提供的文件管理功能,借助编程手段达成对文件名称的批量修改。在这个软件里,…...
【深度学习新浪潮】什么是上下文工程?
什么是上下文工程? 上下文工程(Context Engineering) 是指通过设计、优化与大语言模型(LLM)交互时的输入内容(即“上下文”),引导模型生成更符合预期、更精准回答的系统性方法。这里的“上下文”通常包括 提示词(Prompt)、示例(Few-Shot Examples)、历史对话记录、…...
逆向入门(8)汇编篇-rol指令的学习
还是那个题,这回又碰到个循环左移,有挺多操作方法之前都没有系统的学,用到的时候再看看感觉还挺好,不耽误事 0x00 基本介绍 ROL(Rotate Left): 循环左移,它有两个操作数: 第一个操作数是目标操作数&#…...
Fisco Bcos学习 - 开发第一个区块链应用
文章目录 一、前言二、业务场景分析:简易资产管理系统三、智能合约设计与实现3.1 存储结构设计3.2 接口设计3.3 完整合约代码 四、合约编译与Java接口生成五、SDK配置与项目搭建5.1 获取Java工程项目5.2 项目目录结构5.3 引入Web3SDK5.4 证书与配置文件 六、业务开发…...
黑马python(十六)
目录: 1.JSON数据格式的转换 2.pyecharts模块简介 3.pyecharts入门使用 4.数据准备 5.生成折线图 1.JSON数据格式的转换 2.pyecharts模块简介 官方网站: 画廊网站:有更多的图标形式 测试是否安装 3.pyecharts入门使用 运行会生成一个html的文件&a…...
完成国产化替代!昆明卷烟厂用时序数据库 TDengine 重塑工业时序数据平台
小T导读:昆明卷烟厂作为红云红河烟草(集团)有限责任公司的重要组成部分,是集团卷烟生产的核心工厂。早期在建设制造执行系统(MES)时,其采用了 Wonderware 平台的时序数据存储功能模块࿰…...
50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | BackgroundSlider(背景滑块)
📅 我们继续 50 个小项目挑战!—— BackgroundSlider组件 仓库地址:https://github.com/SunACong/50-vue-projects 项目预览地址:https://50-vue-projects.vercel.app/ 使用 Vue 3 的 Composition API 和 <script setup> …...
Wpf的Binding
前言 wpf的Binding就像一个桥梁,它的作用就是连接逻辑层与界面层,既能够把逻辑层的数据搬到界面层展示,又能将界面层的数据更改后传递到逻辑层,Binding的数据来源就是Binding的源,数据展示的地方就是Binding的目标。 …...
Redis—持久化
持久化 在mysql当中,有4个比较关心的特性,分别是原子性、一致性、隔离性和持久性。这里的持久性和持久化是一回事。我们该如何判断是否具有持久性呢?答案就是看重启进程或者主机之后,数据是否存在。当我们把数据存储在硬盘上是就…...
Spring Boot中日志管理与异常处理
以下是Spring Boot中日志管理与异常处理的系统化实践指南,结合最佳实践与核心配置,确保应用健壮性与可维护性。 📊 一、日志管理核心配置 默认框架与级别控制 Logback 是Spring Boot默认日志框架,通过application.yml快速配置&…...
基于MATLAB的BP神经网络的心电图分类方法应用
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 心电图(ECG)是临床诊断心血管疾病的重要工具,能够反映心脏电活动的周期性变化。…...
【笔记】Docker 配置阿里云镜像加速(公共地址即开即用,无需手动创建实例)
2025年06月25日记 【好用但慎用】Windows 系统中将所有 WSL 发行版从 C 盘迁移到 非系统 盘的完整笔记(附 异常处理)-CSDN博客 【笔记】解决 WSL 迁移后 Docker 出现 “starting services: initializing Docker API Proxy: setting up docker ap” 问题…...
Java 中LinkedList 总结
406.根据身高重建队列 力扣题目链接(opens new window) 假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高…...
微信小程序 / UNIAPP --- 阻止小程序返回(顶部导航栏返回、左 / 右滑手势、安卓物理返回键和调用 navigateBack 接口)
目录 理解page-container的原理 设置禁止点击遮盖层关闭? 阻止左滑返回 理解page-container的原理 page-container组件的所有属性,最重要的是show值。在页面上引入这个组件后,若show值为true,页面上所有各种方式触发的返回操作…...
Linux基本指令篇 —— mv指令
在Windows中我们经常使用CtrlX和CtrlV将一个地方的文件或目录移动到另一个地方,我们若是要在Linux当中完成此操作,则需要使用mv指令。mv 是 Linux 系统中用于移动或重命名文件和目录的基本命令之一,是 "move" 的缩写。下面将详细介…...
基于STM32的智能节能风扇的设计
基于STM32的智能节能风扇的设计 内容:1.摘要 本设计旨在解决传统风扇能耗高、功能单一的问题,提出一种基于STM32的智能节能风扇。通过结合温度传感器、人体红外传感器等多种传感器,利用STM32微控制器实现对风扇的智能控制。经过实际测试,该智…...
HCIA-IP路由基础
前言:本博客仅作记录学习使用,部分图片出自网络,如有侵犯您的权益,请联系删除 本篇笔记是根据B站上的视频教程整理而成,感谢UP主的精彩讲解!如果需要了解更多细节,可以参考以下视频…...
Linux 内存管理之page cache
文章目录 一、page cache1.1 File-backed pages和Anonymous pages1.2 page cache/slab cache1.3 读/写路径1.4 脏页回写1.5 drop_caches1.6 时间局部性与空间局部性1.7 Page Cache 的两种类型1.8 关键数据结构 二、Page Cache 的产生2.1 Buffered I/O(标准 I/O&…...
uniApp实战四:网络请求封装
文章目录 1.最终效果预览2.请求封装3.创建config配置文件4.创建api请求5.页面调用 说明:当前笔记基于Vue3开发,HbuilderX版本4.66 1.最终效果预览 2.请求封装 在util/request.js下创建js文件,代码如下 import config from /configconst tim…...
sentinel 自定义 dashboard 用户名密码
默认情况下,sentinel dashboard 用户名密码为 sentinel / sentinel ,这里我使用重写 镜像的方式: // 定义 Dockerfile $ cat Dockerfile # 基于现有 Sentinel Dashboard 镜像 FROM bladex/sentinel-dashboard:1.8.4# 重新定义 ENTRYPOINT&…...
Fisco Bcos学习 - 搭建星形拓扑组网
文章目录 一、前言二、环境准备与依赖安装2.1 系统要求2.2 依赖安装 三、星形拓扑设计与节点规划四、使用build_chain.sh构建星形拓扑4.1 创建操作目录并获取脚本4.2 生成星形拓扑配置文件4.3 执行构建命令4.4 查看生成的节点文件 五、启动节点与共识验证5.1 启动所有节点5.2 查…...
深度学习入门--(二)感知机
一.感知机是什么 简单的输入和输出,感觉(输入),知道(输出,作出反应) 二.简单逻辑电路 2.1与门 import numpy as np #AND def AND(X1,X2):w1,w2,thera0.5,0.5,0.7tmpX1*w1X2*w2if tmp>the…...
LeetCode 3298.统计重新排列后包含另一个字符串的子字符串数目2
给你两个字符串 word1 和 word2 。 如果一个字符串 x 重新排列后,word2 是重排字符串的 前缀 ,那么我们称字符串 x 是 合法的 。 请你返回 word1 中 合法 子字符串 的数目。 注意 ,这个问题中的内存限制比其他题目要 小 ,所以你…...