绘制学生个人成绩雷达图

绘制雷达图,能直观的反映学生语文数学成绩,并就

  1. 某次考试的成绩和平均成绩进行对比
  2. 期中、期末成绩进行对比展现进步退步情况

目标图

image-3.png
image-4.png

1. 准备工作(已经完成的可以忽略):

还原mysql数据库,为了统一进度。本次数据库,进行统一还原操作

方式可以通过:
cj.sql

  1. phpadmin

  2. 通过命令导入(本次使用)
    通过以下命令将cj.sql传输进ubuntu

scp .\cj.sql xiandai@192.168.120.129:/home/xiandai

随后进入mysql 导入数据库

mysql> create database scores;
Query OK, 1 row affected (0.34 sec)

mysql> use scores;
Database changed

mysql> source /home/xiandai/cj.sql

记得通过 select * from student_scores;

查看导入效果。

2. python获取mysql数据

目标获取数据并转为dataframe形式

导入库(之前学习)

下载库:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple xxxxxxx

# 数据分析三剑客
import numpy as np
import pandas as pd
from pandas import Series,DataFrame
import matplotlib.pyplot as plt
# 处理mysql
import pymysql

数据处理函数(之前学习)

def get_database_connection():
    connection = pymysql.connect(host='192.168.40.105',
                                 user='xd',
                                 password='123',
                                 db='scores',
                                 charset='utf8mb4',
                                 cursorclass=pymysql.cursors.DictCursor)
    return connection, connection.cursor()

获取数据函数并完成数据清洗(之前学习)

将缺考的“-4”进行处理,处理方式是删除。

def get_student_scores():
    """从数据库中获取学生成绩数据并将其转换为 pandas DataFrame。
    Args:
        None
    Returns:
        pandas.DataFrame: 包含学生成绩数据的 DataFrame。
    """
    conn, cursor = get_database_connection()
    try:
        cursor.execute("select * from student_scores;")
        # 一次性获取所有结果
        data = cursor.fetchall()
        # 将数据转换为 DataFrame
        df = pd.DataFrame(data)
        # 将"-4"替换为NaN
        df.replace(-4, np.nan, inplace=True)
        df.dropna(inplace=True)
        return df
    finally:
        conn.close()  # 关闭连接

3. 提取单个学生成绩

# 提取一位学生的中期成绩数据
df=get_student_scores()
# print(df)
labels = ['mid_term_chinese', 'mid_term_math', 'mid_term_english']
student_name='吴欢'
student_data =df[df['name']==student_name].iloc[0]
scores = student_data[labels].values.tolist()
print(scores)

4. 建立雷达图

在绘制雷达图时,我们通常使用极坐标系,而不是常规的直角坐标系。这是因为雷达图是一种用于显示多维数据的图表,它需要在不同方向上显示数据,并且不同方向的数据之间可能存在相关性。而极坐标系能够更自然地展示这种方向性和关联性。

极坐标系与直角坐标系不同,它使用角度和半径来表示数据点的位置,而不是直角坐标系中的x和y坐标。在雷达图中,每个数据点代表一个维度,而角度则表示每个维度的方向,半径则表示数据的大小。这样,我们可以通过在不同角度上放置数据点来展示多个维度的数据,从而形成雷达图的样式。

plt.rcParams['font.sans-serif']=['SimHei']

# 绘制雷达图
fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))

# 对标题和Y轴范围进行处理
ax.set_title(f'{student_name}\'的期中文化课成绩')
ax.set_ylim(0,100) 
labels = ['语文', '数学', '英语']


plt.show()

plt.subplots(figsize=(8, 8)): 这个函数用于创建一个图形(Figure)和一个或多个子图(Subplot)。figsize=(8, 8)参数指定了图形的大小,它是一个元组,包含了图形的宽度和高度,这里设置的是宽度和高度都为8英寸。

subplot_kw=dict(polar=True): 这个参数用于设置子图的属性。polar=True表示要创建一个极坐标系的子图,即雷达图所用到的坐标系类型。

5. 创建角度

在一个圆形的雷达图中,我们目前有三个数据分别是语文数学英语,故我们需要将360°进行三等分。
有个简单的办法,使用之前学习过的np生成等差数列。并转换成列表

angles = np.linspace(0, 2 * np.pi, 3, endpoint=False).tolist()

从0(0°)开始到2π(360°),分成3等分。
np.linspace(0, 2 * np.pi, len(labels), endpoint=False): 这部分代码使用NumPy库中的linspace函数来生成一个从0到2π(即360度)的等差数列,数列的长度为3,即雷达图中每个数据点对应的维度数。endpoint=False参数表示不包含结束点,即不包含2π。

.tolist(): 将生成的NumPy数组转换为Python列表。这样做是因为后续的绘图函数需要接受一个列表作为参数。

6. 画图

ax.fill(angles, scores, color='red', alpha=0.25) #画的是内部
ax.plot(angles, scores, color='red', linewidth=2) #画的是外部轮廓

image-nhhr.png

7. 问题修改

图中数据未“闭合”,我们调整数据使其更加美观

angles = np.linspace(0, 2 * np.pi, 3, endpoint=False).tolist()

scores += scores[:1]
angles += angles[:1]

scores += scores[:1]: 这行代码将列表scores的第一个元素(即scores[0])添加到列表末尾。这样做的效果是将首尾两个数据点连接起来,形成一个闭合的多边形。这是因为雷达图中,首尾两个数据点应该是连接在一起的,以便形成一个完整的图形。
例如,如果原始的scores列表为[72, 56, 71],那么经过这行代码处理后,scores列表将变为[72, 56, 71, 72],即在末尾添加了首个元素,使得数据点首尾连接。
image-1.png

8. 美化,添加标签

标签

# 添加标签
ax.set_xticks(angles[:-1])
ax.set_xticklabels(labels)
ax.set_xticklabels(['语文','数学','英语']) # 清空Y轴刻度标签

在数据上进行标注

# 在Y轴上添加文本标签
for i in range(3):
    ax.text(angles[i], scores[i]+5, str(int(scores[i])), color='black', fontsize=10, ha='center', va='bottom')

image-2.png

阶段整理

至此我们完成了单个学生的成绩雷达图,我们需要对绘图部分封装成函数

import numpy as np
import pandas as pd
from pandas import Series,DataFrame
import matplotlib.pyplot as plt

import pymysql

def get_database_connection():
    connection = pymysql.connect(host='192.168.40.105',
                                 user='xd',
                                 password='123',
                                 db='scores',
                                 charset='utf8mb4',
                                 cursorclass=pymysql.cursors.DictCursor)
    return connection, connection.cursor()

def get_student_scores():
    conn, cursor = get_database_connection()
    try:
        cursor.execute("select * from student_scores;")
        data = cursor.fetchall()
        df = pd.DataFrame(data)
        df.replace(-4, np.nan, inplace=True)
        df.dropna(inplace=True)
        return df
    finally:
        conn.close()

df = get_student_scores()

# 提取一位学生的中期成绩数据
student_name = '吴欢'  # 选择要绘制雷达图的学生
student_data = df[df['name'] == student_name].iloc[0]
scores = student_data[['mid_term_chinese', 'mid_term_math', 'mid_term_english']].values.tolist()


# 绘制雷达图
plt.rcParams['font.sans-serif']=['SimHei']
fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))
ax.set_title(f'{student_name}的期中文化课成绩')
ax.set_ylim(0,100) 
labels = ['语文', '数学', '英语']

# 创建角度
angles = np.linspace(0, 2 * np.pi, 3, endpoint=False).tolist()
# angles = [angle + np.pi/2 for angle in angles]
scores += scores[:1]
angles += angles[:1]
ax.fill(angles, scores, color='red', alpha=0.25)
ax.plot(angles, scores, color='red', linewidth=2)

# 添加标签
ax.set_yticklabels([])  # 清空Y轴刻度标签
ax.set_xticks(angles[:-1])
ax.set_xticklabels(labels)
# 在Y轴上添加文本标签
for i in range(3):
    ax.text(angles[i], scores[i]+5, str(int(scores[i])), color='black', fontsize=10, ha='center', va='bottom')

# 显示图形
plt.show()

封装后

import numpy as np
import pandas as pd
from pandas import Series,DataFrame
import matplotlib.pyplot as plt

import pymysql

def get_database_connection():
    connection = pymysql.connect(host='192.168.40.105',
                                 user='xd',
                                 password='123',
                                 db='scores',
                                 charset='utf8mb4',
                                 cursorclass=pymysql.cursors.DictCursor)
    return connection, connection.cursor()

def get_student_scores():
    conn, cursor = get_database_connection()
    try:
        cursor.execute("select * from student_scores;")
        data = cursor.fetchall()
        df = pd.DataFrame(data)
        df.replace(-4, np.nan, inplace=True)
        df.dropna(inplace=True)
        return df
    finally:
        conn.close()

def draw_stu(df,student_name):
    student_data = df[df['name'] == student_name].iloc[0]
    scores = student_data[['mid_term_chinese', 'mid_term_math', 'mid_term_english']].values.tolist()


    # 绘制雷达图
    plt.rcParams['font.sans-serif']=['SimHei']
    fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))
    ax.set_title(f'{student_name}的期中文化课成绩')
    ax.set_ylim(0,100) 
    labels = ['语文', '数学', '英语']

    # 创建角度
    angles = np.linspace(0, 2 * np.pi, 3, endpoint=False).tolist()
    # angles = [angle + np.pi/2 for angle in angles]
    scores += scores[:1]
    angles += angles[:1]
    ax.fill(angles, scores, color='red', alpha=0.25)
    ax.plot(angles, scores, color='red', linewidth=2)

    # 添加标签
    ax.set_yticklabels([])  # 清空Y轴刻度标签
    ax.set_xticks(angles[:-1])
    ax.set_xticklabels(labels)
    # 在Y轴上添加文本标签
    for i in range(3):
        ax.text(angles[i], scores[i]+5, str(int(scores[i])), color='black', fontsize=10, ha='center', va='bottom')

    # 显示图形
    plt.show()

df = get_student_scores()
draw_stu(df,'吴欢')

9.修改主函数,使得能绘制班级所有学生的雷达图

将显示改为保存

    # 显示图形
    # plt.show()
    plt.savefig(student_name+'.png')
    plt.close('all')

步骤一:利用之前学习的questionary

需要安装,下载库:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple xxxxxxx

from questionary import select

步骤二:创建选择

question = select("选择分析的班级:", choices=["23401", "23402", "23403"])
choice_class = question.ask()
print("您选择的班级是:", choice_class)

步骤五:根据选择,调整整个代码

主程序部分:

question = select("选择分析的班级:", choices=["23401", "23402", "23403"])
choice_class = question.ask()
print("您选择的班级是:", choice_class)

df=df[df['class']==choice_class]

stu_list=df['name'].tolist()
for student_name in stu_list:
    draw_stu(df,student_name)

函数修改:将显示改为保存

    # 显示图形
    # plt.show()
    plt.savefig(student_name+'.png')
    plt.close('all')

10. 修改draw_stu函数,使其再画上平均成绩

在draw_stu中

    # 计算各科目平均成绩
    average_scores=df[['mid_term_chinese', 'mid_term_math', 'mid_term_english']].mean().values.tolist()

    #。。。。。

    # 绘制平均成绩图
    average_scores += average_scores[:1]
    ax.fill(angles, average_scores, color='#364F6B', alpha=0.5)
    ax.plot(angles, average_scores, color='#364F6B', linewidth=1,label='平均成绩')

    #。。。。。


    # 在Y轴上添加平均成绩文本标签
    for i in range(3):
        ax.text(angles[i]+0.15, average_scores[i]+5, str(int(average_scores[i])), color='#364F6B', fontsize=10, ha='center', va='bottom')
    
    # 为了区分加上标签
    ax.legend(loc='best')

补充

为了美观,我调整了坐标轴方向

    # 创建角度
    angles = np.linspace(0, 2 * np.pi, 3, endpoint=False).tolist()
    
    # 为了美观,可以加上这条
    angles = [angle + np.pi/2 for angle in angles]

完整代码

import numpy as np
import pandas as pd
from pandas import Series,DataFrame
import matplotlib.pyplot as plt
from questionary import select
import pymysql

def get_database_connection():
    connection = pymysql.connect(host='192.168.40.105',
                                 user='xd',
                                 password='123',
                                 db='scores',
                                 charset='utf8mb4',
                                 cursorclass=pymysql.cursors.DictCursor)
    return connection, connection.cursor()

def get_student_scores():
    conn, cursor = get_database_connection()
    try:
        cursor.execute("select * from student_scores;")
        data = cursor.fetchall()
        df = pd.DataFrame(data)
        df.replace(-4, np.nan, inplace=True)
        df.dropna(inplace=True)
        return df
    finally:
        conn.close()

def draw_stu(df,student_name):
    student_data = df[df['name'] == student_name].iloc[0]
    scores = student_data[['mid_term_chinese', 'mid_term_math', 'mid_term_english']].values.tolist()
    average_scores=df[['mid_term_chinese', 'mid_term_math', 'mid_term_english']].mean().values.tolist()

    # 绘制雷达图
    plt.rcParams['font.sans-serif']=['SimHei']
    fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))
    ax.set_title(f'{student_name}的期中文化课成绩')
    ax.set_ylim(0,100) 
    labels = ['语文', '数学', '英语']

    # 创建角度
    angles = np.linspace(0, 2 * np.pi, 3, endpoint=False).tolist()
    
    # 为了美观,可以加上这条
    angles = [angle + np.pi/2 for angle in angles]
    
    
    scores += scores[:1]
    angles += angles[:1]
    average_scores += average_scores[:1]
    ax.fill(angles, average_scores, color='#364F6B', alpha=0.5)
    ax.plot(angles, average_scores, color='#364F6B', linewidth=1,label='平均成绩')
    
    ax.fill(angles, scores, color='#891652', alpha=0.25)
    ax.plot(angles, scores, color='#891652', linewidth=1,label=student_name+'成绩')

    # 添加标签
    ax.set_yticklabels([])  # 清空Y轴刻度标签
    ax.set_xticks(angles[:-1])
    ax.set_xticklabels(labels)
    # 在Y轴上添加文本标签
    for i in range(3):
        ax.text(angles[i], scores[i]+5, str(int(scores[i])), color='#891652', fontsize=10, ha='center', va='bottom')

    # 在Y轴上添加文本标签
    for i in range(3):
        ax.text(angles[i]+0.15, average_scores[i]+5, str(int(average_scores[i])), color='#364F6B', fontsize=10, ha='center', va='bottom')

    ax.legend(loc='best')

    # 显示图形
    # plt.show()
    plt.savefig(student_name+'.png')
    plt.close('all')
    return

df = get_student_scores()
question = select("选择分析的班级:", choices=["23401", "23402", "23403"])
choice_class = question.ask()
print("您选择的班级是:", choice_class)

df=df[df['class']==choice_class]

stu_list=df['name'].tolist()
for student_name in stu_list:
    draw_stu(df,student_name)