下边代码部分写的有点冗余、鲁棒性一般,所发内容均是为了记录自己在此方向上的成长过程,实力欠缺,大家多多包涵!!!
题目
项目名称:学生成绩数据可视化项目
项目时间:一天
项目成绩:100分,占总成绩的60%
项目提交:源码+效果视频
项目功能:
根据给定的学生成绩进行数据统计与分析,客户端界面如下:
**********************************************
|学生成绩管理系统|
-------------------------
***************1统计数据********************
***************2查询成绩********************
***************3绘制图表********************
**********************************************
基本要求(80分):
- 客户端根据输入的指令,发送给服务器,服务器进行相应的动作响应
- 统计数据:将每门课的最高分、最低分、平均分、及格率进行统计,并存放在当前excel表的新sheet中,服务器将数据统计完成后给客户端回复"统计完成"
- 查询成绩:可以根据人名进行查询课程成绩,也可以查看每门课的课程名称查看最高分、最低分、平均分、及格率,将查找到的内容在客户端运行窗口进行显示
- 绘制图表:根据每门课的及格率绘制饼图;根据C语言成绩的前5名绘制成绩走势折线图;服务器绘制完成后给客户端回复“绘制完成”,并将图片数据回复给客户端,客户端进行绘制图像
扩展功能(20分):
- 实现并发服务器(多个客户端可以连接一个服务器)
client:
import time
import numpy as np
import pandas as pd
import socket as st
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
def user_face():
# 封装封面
print("************************************************")
print(" |学生成绩管理系统|")
print(" -------------------------")
print(" ***************1统计数据********************")
print(" ***************2查询成绩********************")
print(" ***************3绘制图表********************")
print(" ***************4关闭程序********************")
print("************************************************")
if __name__ == '__main__':
# 进行客服端与服务器的连接
socket1 = st.socket(st.AF_INET, st.SOCK_STREAM)
socket1.connect(('192.168.0.106', 7777))
print('连接到服务器')
off = '与服务器连接成功'
socket1.send(off.encode()) # 传递给服务器确保连接成功
time.sleep(1)
user_face()
# 开始实现项目功能
while True:
op = input("请输入指令1,2,3,4指定查询内容")
if op == '4':
socket1.sendall(op.encode()) # 发送数据
socket1.close() # 关闭socket连接
break
elif op not in ['1', '2', '3']:
print("请输入正确指令")
continue
socket1.sendall(op.encode()) # 发送数据
if op == '1':
data = socket1.recv(1024).decode() # 接收数据
print(data)
elif op == '2':
name = input("请输入要查看成绩的姓名或课程名称:")
socket1.sendall(name.encode())
print(socket1.recv(1024).decode())
elif op == '3':
# fig = socket1.recv(1024).decode()
arr = plt.imread('./figure1.png')
plt.imshow(arr)
plt.show()
continue
exit()
server:
import numpy as np
import pandas as pd
import socket as st
import time
import json
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
from openpyxl import load_workbook
from openpyxl.utils.dataframe import dataframe_to_rows
# 设置中文显示的字体
plt.rcParams['font.sans-serif'] = ['SimHei']
# 解决负号显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
# 读取
all_sheets = pd.read_excel('./学生成绩信息表.xlsx', sheet_name=['C基础', '数据结构', 'STM32', 'C++'],
usecols=['姓名', '考试成绩'])
def sheet():
"""
构建一个只有成绩和姓名的dataframe
:return: merged_data
"""
# 为每个工作表的成绩列添加课程名称作为后缀,避免合并后列名冲突
for course in all_sheets:
# 重命名考试成绩列为"课程名_成绩"
all_sheets[course] = all_sheets[course].rename(
columns={'考试成绩': f'{course}_成绩'}
)
# 从第一个工作表开始,逐步合并所有表
# 初始合并结果为第一个工作表数据
df_merged = all_sheets['C基础']
# 依次合并其他工作表
for course in ['数据结构', 'STM32', 'C++']:
df_merged = df_merged.merge(
all_sheets[course], # 要合并的表
on='姓名', # 合并键(姓名)
how='outer' # 外连接:保留所有表中的姓名
)
# 未设置姓名索引前数据
df = df_merged
# 设置姓名为索引
df_merged.set_index('姓名', inplace=True)
return df, df_merged
def tongji():
df, df_merged = sheet()
# 统计每门课的最高分、最低分、平均分和及格率
stats = pd.DataFrame()
stats['最高分'] = df_merged.max()
stats['最低分'] = df_merged.min()
stats['平均分'] = df_merged.mean()
stats['及格率'] = (df_merged >= 70).sum() / df_merged.shape[0]
# stats1 = stats.T
# 将人与各门特殊值连接,生成一个表
# ret = pd.concat([df_merged, stats1], axis=0)
with pd.ExcelWriter('./学生成绩信息表.xlsx', mode='a', if_sheet_exists='replace', engine='openpyxl') as writer:
stats.to_excel(writer, sheet_name="汇总")
temp = pd.read_excel('./学生成绩信息表.xlsx', sheet_name=['汇总'], usecols=['及格率'])
return stats
def search_grade(name):
"""
查询成绩
:param name:
:return:
"""
# 引用自定义的函数,引用其数据
df1, df_merged1 = sheet()
stats1 = tongji()
# 转置一下,dataframe无法输出行
ret = stats1.T
ret1 = df_merged1.T
# 查询各科成绩特点
if name in ret.columns:
zhi = ret[name]
return zhi
# 查询个人分数
elif name in ret1.columns:
zhi = ret1[name]
return zhi
def draw():
# 创建画布
fig = plt.figure(figsize=(8, 5))
# 创建2行5列的网格布局,但第二行将合并为一个子图
gs = GridSpec(2, 4, figure=fig, height_ratios=[1, 1]) # 两行高度比例相同
# 第二行的1个整体子图,横跨所有5列
ax_bottom = fig.add_subplot(gs[1, :]) # 第二行,所有列
ax_bottom.set_title('5')
# 调整布局间距
plt.tight_layout()
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1])
ax3 = fig.add_subplot(gs[0, 2])
ax4 = fig.add_subplot(gs[0, 3])
# 设置支持中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
# 调用统计里及格率的参数
temp1 = tongji()
temp = temp1['及格率']
# 绘制C语言、数据结构、stm32、C++ 及格率饼图
ax1.pie([temp['C基础_成绩'], 1 - temp['C基础_成绩']], autopct='%0.2f%%', labels=['及格率', '不及格率'])
ax1.set_title('C基础及格率')
ax2.pie([temp['数据结构_成绩'], 1 - temp['数据结构_成绩']], autopct='%0.2f%%', labels=['及格率', '不及格率'])
ax2.set_title('数据结构及格率')
ax3.pie([temp['STM32_成绩'], 1 - temp['STM32_成绩']], autopct='%0.2f%%', labels=['及格率', '不及格率'])
ax3.set_title('STM32及格率')
ax4.pie([temp['C++_成绩'], 1 - temp['C++_成绩']], autopct='%0.2f%%', labels=['及格率', '不及格率'])
ax4.set_title('C++及格率')
# 前五名成绩获取
df, df_merged = sheet()
num = df_merged
a = num[::][:5]
print(a)
# 第二行,所有列
ax5 = fig.add_subplot(gs[1, :])
# 根据C语言成绩的前5名绘制成绩走势折线图
# 定义线条样式和颜色
styles = ['-o', '-s', '-^', '-D']
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728']
# 绘制每门课程的折线图
for i, column in enumerate(a.columns):
plt.plot(a.index, a[column], styles[i], color=colors[i], label=column)
# 添加标题和标签
plt.title('各门课程成绩走势')
plt.xlabel('学生姓名')
plt.ylabel('成绩')
# 设置Y轴范围,使图表右侧显示成绩
plt.ylim(50, 130)
# 添加图例
plt.legend(fontsize=8)
# 调整布局
# plt.tight_layout()
ax = plt.gca()
# 保存图片为文件
plt.savefig('./figure1.png')
return
if __name__ == '__main__':
# 建立连接
sfd = st.socket(st.AF_INET, st.SOCK_STREAM)
sfd.bind(('192.168.0.106', 7777))
# sfd.connect(('192.168.0.106', 7777))
sfd.listen(10)
socket1, addr = sfd.accept()
off = socket1.recv(60)
# 接收客户端,查看是否连接成功
print(off.decode())
# 等待一下,防止时间偏
time.sleep(1)
while True:
# 接收命令
zhi_xing = socket1.recv(60)
ming_ling = zhi_xing.decode()
# 对命令进行相应操作
if ming_ling == '4':
break
if ming_ling == '1':
df0, df_merged0 = sheet()
stats0 = tongji()
one_data = '统计完成'
# 1. 将字典序列化为JSON字符串
json_str2 = json.dumps(one_data, ensure_ascii=False)
# 2. 转换为字节流(UTF-8编码)
bytes_data2 = json_str2.encode('utf-8')
socket1.sendall(bytes_data2)
elif ming_ling == '2':
# 接收完整代码
# received_code = sfd.recv(1024).decode()
# data0 = exec(received_code) # 直接执行,无需 seed
# data0 = sfd.recv(1024).decode()
data0 = socket1.recv(60).decode()
result0 = search_grade(data0)
# 1. 将字典序列化为JSON字符串
# json_str = json.dumps(result0, ensure_ascii=False)
# 2. 转换为字节流(UTF-8编码)
# bytes_data = json_str.encode('utf-8')
# 发送数据
# socket1.sendall(result0)
# socket1.sendall(result0.encode())
# return0为Series对象,应先转化为字典
result1 = result0.to_dict()
json_str = json.dumps(result1, ensure_ascii=False)
bytes_data5 = json_str.encode('utf-8')
socket1.sendall(bytes_data5)
print("字典数据已发送")
elif ming_ling == '3':
draw()
arr0 = plt.imread('./figure1.png')
# 1. 将字典序列化为JSON字符串
# json_str1 = json.dumps(arr0, ensure_ascii=False)
# 2. 转换为字节流(UTF-8编码)
# bytes_data1 = json_str1.encode('utf-8')
# 发送数据
# socket1.sendall(bytes_data1)
continue
exit()