飞码网-免费源码博客分享网站

点击这里给我发消息

用Python和Pandas做一个成绩册

飞码网-免费源码博客分享网站 爱上飞码网—https://www.codefrees.com— 飞码网-matlab-python-C++ 爱上飞码网—https://www.codefrees.com— 飞码网-免费源码博客分享网站

目录

  • 演示:您将构建的内容
  • 项目概况
  • 背景阅读
  • 探索此pandas项目的数据
  • 确定数据的最终格式
  • 用pandas加载数据
    • 加载花名册文件
    • 加载作业和考试文件
    • 加载测验文件
  • 合并成绩数据框
    • 合并名册和作业成绩
    • 合并测验成绩
    • 填写nan值
  • 使用Pandas DataFrame计算成绩
    • 计算考试总分
    • 计算作业分数
    • 计算测验分数
    • 计算字母等级
  • 分组数据
  • 绘制摘要统计
  • 结论

所有教师共同的工作之一是评价学生无论您使用考试,家庭作业,测验还是项目,通常都必须在学期末将学生的分数变成字母等级这通常涉及您可能在电子表格中进行的一系列计算。相反,您可以考虑使用Python和pandas。

使用电子表格的一个问题是,当您在公式中输入错误时可能很难看到。也许您选择了错误的列,然后将测验放在应该去的地方。也许您发现了两个错误值的最大值。要解决此问题,您可以使用Python和pandas进行所有计算,并更快地找到并修复这些错误。

在本教程中,您将学习如何

  • 使用pandas加载合并来自多个来源的数据
  • 在pandas DataFrame中过滤分组数据
  • 在pandasDataFrame中计算绘制成绩

演示:您将构建的内容

在这个pandas项目中,您将创建一个Python脚本来加载成绩数据并计算学生的字母成绩。

您的脚本将从命令行或IDE运行,并生成CSV输出文件,因此您可以将成绩粘贴到学校的评分系统中。您还将制作一些曲线图,以查看成绩的分布方式。

项目概况

这个pandas项目涉及四个主要步骤:

  1. 探索您将在项目中使用的数据,以确定计算最终成绩所需的格式和数据。
  2. 将数据加载到pandas DataFrames中,确保在所有数据源中连接同一学生的成绩。
  3. 计算最终成绩并将其保存为CSV文件。
  4. 绘制成绩分布图,并探索学生之间的成绩差异。

完成这些步骤后,您将拥有一个可以计算成绩的Python脚本。您的成绩将采用您应该能够上传到学校的学生管理系统中的格式。

背景阅读

如果您有一些与pandas合作的经验,那么您将充分利用此pandas项目。如果您需要复习,那么这些教程和课程将使您快速熟悉该项目:

  • Pandas DataFrame:使处理数据令人愉快
  • 基本的pandas数据结构
  • pandas:如何读取和写入文件
  • 用pandas读取CSV
  • 在pandas相结合的数据merge().join()concat()

不必太担心记住这些教程中的所有细节。您将在此pandas项目中看到主题的实际应用。现在,让我们看看您将在该项目中使用的数据!

探索此pandas项目的数据

与大多数老师一样,本学期您可能使用了多种服务来管理您的班级,包括:

  • 学校的学生管理系统
  • 一项用于管理作业和考试的分配和评分的服务
  • 管理分配测验和评分测验的服务

就本项目而言,您将使用示例数据,这些数据代表您可能从这些系统中获得的信息。数据位于逗号分隔值(CSV)文件中。这里显示了一些数据样本。首先,有一个包含类名册信息的文件。这些来自您的学生管理系统:

ID 名称 网络ID 电子邮件地址 部分
1234567 “小Barrera,伍迪” WXB12345 WOODY.BARRERA_JR@UNIV.EDU 1个
2345678 “兰伯特,马拉凯” MXL12345 MALAIKA.LAMBERT@UNIV.EDU 2
3456789 “乔伊斯,崔西” TXJ12345 TRACI.JOYCE@UNIV.EDU 1个
4567890 “花,约翰·格雷格” JGF12345 JOHN.G.2.FLOWER@UNIV.EDU 3

该表指示每个学生的ID号,姓名,NetID和电子邮件地址,以及他们所属班级的部分。在本学期中,您教了一个在不同时间开会的班级,每个班级时间都有不同的科目编号。

接下来,您将拥有一个包含作业和考试成绩的文件。这是来自家庭作业和考试评分服务的列,其排列方式与名册略有不同:

SID 名字 作业1 作业1-最高分 作业1-提交时间
jgf12345 格雷格 69 80 2019-08-29 08:56:02-07:00
mxl12345 马拉卡 兰伯特 63 80 2019-08-29 08:56:02-07:00
txj12345 崔西 乔伊斯   80 2019-08-29 08:56:02-07:00
wxb12345 伍迪 巴雷拉 55 80 2019-08-29 08:56:02-07:00

在此表中,每个学生都有一个SID,名字和姓氏。此外,您为每项家庭作业和考试报告了三个值:

  1. 学生获得分数
  2. 作业最高分数
  3. 学生提交作业时间

最后,您有包含测验成绩信息的文件。这些文件是分开的,以便每个数据文件中存储一个测验,并且这些文件中的信息与名册和作业文件不同:

名字 电子邮件 年级
巴雷拉 伍迪 woody.barrera_jr@univ.edu 4
约翰 john.g.2.flower@univ.edu 8
乔伊斯 崔西 traci.joyce@univ.edu 8
兰伯特 马拉卡 malaika.lambert@univ.edu 8

在测验表中,每个学生都有姓,名,电子邮件和测验成绩。请注意,最大可能的测验分数未存储在此表中。稍后您将看到如何提供该信息。

检查此数据,您可能会注意到几个功能:

  • 每个表都有不同的学生姓名表示例如,在名册表中,名称采用"Last Name, First Name"带引号的形式,因此CSV解析器不会将逗号解释为新列。但是,在作业表中,名字和姓氏各有其各自的列。

  • 每个学生在不同的数据源中可能使用不同的名称例如,测验表不包含Jr.伍迪·巴雷拉(Woody Barrera)名称的后缀另一个例子是John Flower喜欢用他的中间名Gregg来称呼,因此他调整了作业表中的显示。

  • 每个学生的电子邮件地址没有相同的元素学生的基本电子邮件地址是first.last@univ.edu但是,如果另一位学生已经拥有该格式的电子邮件,则该电子邮件地址将被修改为唯一。这意味着您不能仅根据学生的姓名来预测他们的电子邮件地址。

  • 即使每列具有相同的数据,也可以使用唯一的名称例如,所有学生都具有形式的标识符abc12345名册表将此称为其NetID,而作业表将其称为SID。测验表根本没有此信息。同样,某些表使用列标题Email address,而其他表仅使用Email

  • 每个表对数据进行不同的排序在名册表中,数据按ID排序在作业表中,数据按名字的首字母排序。在测验表中,数据以随机顺序排序。

  • 表中的每一行或每一列可能都缺少数据例如,崔西·乔伊斯(Traci Joyce)没有提交她的作业1,因此她的行在作业表中为空白。

所有这些功能以及更多功能都将出现在现实世界中的数据中。在此pandas项目的其余部分中,您将看到如何解决所有这些功能,并确保它们不会破坏您的分析。

确定数据的最终格式

既然您已经看到了原始数据格式,那么您可以考虑一下数据的最终格式。最后,您需要根据每个学生的原始分数计算其字母等级。最终数据表中的每一行将包含单个学生的所有数据。行数将等于班级的学生人数。

这些列将代表每个作业分数,测验分数和考试分数。您还将存储有关每个学生的一些信息,包括他们的姓名和唯一标识符。最后,您将把每个计算结果和最终字母等级存储在单独的列中。

这是您的决赛桌示例:

识别码 名称 家庭作业 测验 考试 最终分数 期末成绩
学生1 最后,第一 A–F
学生2 最后,第一 A–F

表中的每一行都存储单个学生的所有数据。第一列具有学生的唯一标识符,第二列具有学生的姓名。然后一系列列存储作业,测验,考试和最终成绩。最后是最后一年的一栏。

既然您已经了解了数据的最终形状,那么就可以开始使用数据了。第一步是加载数据!

用pandas加载数据

创建一个名为的Python脚本gradebook.py您还需要创建一个名为的文件夹data该文件夹将存储您的成绩簿脚本的输入数据文件。

然后,在中gradebook.py,首先添加一个解释文件用途的模块级文档字符串。您现在也可以导入一些库:

"""Calculate student grades by combining data from many sources.

Using pandas, this script combines data from the:

* Roster
* Homework & Exam grades
* Quiz grades

to calculate final grades for a class.
"""
from pathlib import Path
import pandas as pd

在此代码中,包含描述脚本目的的文档字符串。然后导入pathlib.Pathpandas

加载花名册文件

现在,您可以从花名册开始加载数据:

HERE = Path(__file__).parent
DATA_FOLDER = HERE / "data"

roster = pd.read_csv(
    DATA_FOLDER / "roster.csv",
    converters={"NetID": str.lower, "Email Address": str.lower},
    usecols=["Section", "Email Address", "NetID"],
    index_col="NetID",
)

在此代码中,您将创建两个常量HEREDATA_FOLDER,以跟踪当前正在执行的文件以及存储数据的文件夹的位置。这些常量使用pathlib模块使引用不同文件夹变得容易。

然后,使用读取名册文件pd.read_csv()为了帮助以后处理数据,您可以使用设置索引,index_col并仅使用包含有用的列usecols

为了确保以后可以比较字符串,还传递了converters将列转换为小写字母参数。这将简化您稍后将进行的字符串比较。

您可以在roster下面DataFrame中看到一些数据

网络ID 电子邮件地址 部分
wxb12345 woody.barrera_jr@univ.edu 1个
mxl12345 malaika.lambert@univ.edu 2
txj12345 traci.joyce@univ.edu 1个
jgf12345 john.g.2.flower@univ.edu 3

这些是的前四行roster,它们与您在上一节中查看的名册表中的行匹配。但是,NetIDEmail Address列都已转换为小写字符串,因为您为这两列传递str.lowerconverters您还省略了NameID列。

加载作业和考试文件

接下来,您可以加载作业和考试成绩CSV文件。请记住,此文件除所有成绩外,还包括名字和姓氏以及SID列。您想忽略带有提交时间的列:

hw_exam_grades = pd.read_csv(
    DATA_FOLDER / "hw_exam_grades.csv",
    converters={"SID": str.lower},
    usecols=lambda x: "Submission" not in x,
    index_col="SID",
)

在此代码中,再次使用converters参数将SIDandEmail Address列中的数据转换为小写。尽管这些列中的数据在初次检查时看起来是小写的,但是最佳实践是确保所有内容都是一致的。您还需要指定SID作为索引列以匹配roster DataFrame。

在此CSV文件中,有许多列包含作业提交时间,您将不会在任何进一步的分析中使用它们。但是,还有很多其他列要保留,因此要显式列出所有列会很繁琐。

为了解决这个问题,usecols还接受使用一个参数(列名)调用的函数。如果函数返回True,则包含该列。否则,将排除该列。使用此处传递lambda函数,如果字符串"Submission"出现在列名称中,则该列将被排除。

以下是hw_exam_gradesDataFrame的示例,可让您了解数据加载后的外观:

SID 作业1 作业1-最高分 作业2
jgf12345 69 80 52
mxl12345 63 80 57
txj12345 80 77
wxb12345 55 80 62

这些是您在上一节中看到的家庭作业和考试成绩CSV文件中示例学生的行。请注意,该列中Traci Joyce(SID txj12345的缺失数据Homework 1被读取为nan或Not Number值。您将在后面的部分中看到如何处理此类数据。省略号(...)表示此处未在示例中显示但从实际数据加载的数据列。

加载测验文件

最后,您需要从测验中加载数据。您需要阅读五个测验,并且此数据最有用的形式是单个DataFrame,而不是五个单独的DataFrame。最终的数据格式将如下所示:

电子邮件 测验5 测验2 测验4 测验1 测验3
woody.barrera_jr@univ.edu 10 10 7 4 11
john.g.2.flower@univ.edu 5 8 13 8 8
traci.joyce@univ.edu 4 6 9 8 14
malaika.lambert@univ.edu 6 10 13 8 10

此DataFrame的Email列为索引,每个测验位于单独的列中。请注意,测验是乱序的,但是当您计算最终成绩时,您会看到顺序无关紧要。您可以使用以下代码加载测验文件:

quiz_grades = pd.DataFrame()
for file_path in DATA_FOLDER.glob("quiz_*_grades.csv"):
    quiz_name = " ".join(file_path.stem.title().split("_")[:2])
    quiz = pd.read_csv(
        file_path,
        converters={"Email": str.lower},
        index_col=["Email"],
        usecols=["Email", "Grade"],
    ).rename(columns={"Grade": quiz_name})
    quiz_grades = pd.concat([quiz_grades, quiz], axis=1)

在此代码中,您将创建一个名为的空DataFrame quiz_grades您需要空的DataFrame的原因与使用之前需要创建空列表的原因相同list.append()

您可以Path.glob()用来查找所有测验CSV文件并使用大pandas加载它们,并确保将电子邮件地址转换为小写字母。您还可以将每个测验的索引列设置为学生的电子邮件地址,该地址pd.concat()用于对齐每个学生的数据。

请注意,您传递axis=1pd.concat()这导致pandas连接列而不是行,从而将每个新测验添加到组合DataFrame中的新列中。

最后,您可以DataFrame.rename()将“成绩”列的名称从Grade更改为每个测验的特定名称

合并成绩数据框

现在你已经加载的所有数据,你可以从你的三个DataFrames结合数据rosterhw_exam_grades以及quiz_grades这样一来,就可以使用一个DataFrame进行所有计算,最后将完整的成绩簿保存为另一种格式。

分两步将数据合并在一起:

  1. 合并 rosterhw_exam_grades合并到一个名为的新DataFrame中final_data
  2. 合并 final_dataquiz_grades在一起。

您将在每个DataFrame中使用不同的列作为merge键,这就是pandas确定将哪些行保持在一起的方式。此过程是必要的,因为每个数据源为每个学生使用不同的唯一标识符。

合并名册和作业成绩

roster和中hw_exam_grades,您将NetID或SID列作为给定学生的唯一标识符。在pandas中合并或连接DataFrame时,使用index最为有用。您已经知道加载测验文件时这很有用。

请记住,您将index_col参数传递pd.read_csv()加载名册和作业成绩的时间。现在,您可以将这两个DataFrame合并在一起:

final_data = pd.merge(
    roster, hw_exam_grades, left_index=True, right_index=True,
)

在此代码中,您用于pd.merge()组合roster和和hw_exam_gradesDataFrame。

这是四个示例学生的合并DataFrame的示例:

网络ID 电子邮件地址 作业1
wxb12345 woody.barrera_jr@univ.edu 55
mxl12345 malaika.lambert@univ.edu 63
txj12345 traci.joyce@univ.edu
jgf12345 john.g.2.flower@univ.edu 69

就像您之前看到的那样,省略号表示此处的示例中未显示但实际DataFrame中存在的列。样本表显示具有相同NetID或SID的学生已合并在一起,因此他们的电子邮件地址和Homework 1成绩与您之前看到的表匹配。

合并测验成绩

加载的数据时quiz_grades,您将电子邮件地址用作每个学生的唯一标识符。这与hw_exam_gradesroster分别使用NetID和SID的不同

要合并quiz_grades到中final_data,可以使用from中的索引quiz_gradesfrom中Email Addressfinal_data

final_data = pd.merge(
    final_data, quiz_grades, left_on="Email Address", right_index=True
)

在此代码中,您使用left_on参数来pd.merge()告诉pandas在合并中使用Email Addressfinal_data您还right_index可以告诉pandasquiz_grades在合并中使用索引

这是合并的DataFrame的示例,显示了四个示例学生:

网络ID 电子邮件地址 作业1 测验3
wxb12345 woody.barrera_jr@univ.edu 55 11
mxl12345 malaika.lambert@univ.edu 63 10
txj12345 traci.joyce@univ.edu 14
jgf12345 john.g.2.flower@univ.edu 69 8

请记住,省略号表示此处的示例表中缺少列,但这些列将出现在合并的DataFrame中。您可以仔细检查前面的表格,以验证数字是否与正确的学生对齐。

填写nan

现在,您的所有数据都合并到一个DataFrame中。在继续计算成绩之前,您需要再进行一点数据清理。

您可以在上表中看到Traci Joyce的nan作业1仍然有价值。您不能nan在计算中使用值,因为它们不是数字!您可以用来DataFrame.fillna()为中的所有nan分配一个数字final_data

final_data = final_data.fillna(0)

在这段代码中,你用DataFrame.fillna()填满所有nanfinal_data与价值0这是一个适当的解决方案,因为nanTraci Joyce的“作业1”列中的值表示该分数丢失,这意味着她可能没有上交作业。

这是修改后的DataFrame的示例,显示了四个示例学生:

网络ID 名字 作业1
wxb12345 伍迪 巴雷拉 55
mxl12345 马拉卡 兰伯特 63
txj12345 崔西 乔伊斯 0
jgf12345 约翰 69

如您在此表中所见,Traci Joyce的作业1得分现在0nan,而不是,但是其他学生的成绩没有改变。

使用Pandas DataFrame计算成绩

您在课堂上有三类作业:

  1. 考试
  2. 家庭作业
  3. 测验

这些类别中的每一个都被赋予学生最终分数权重对于本学期的课程,您分配了以下权重:

类别 最终成绩百分比 重量
考试1分数 5 0.05
考试2分数 10 0.10
考试3分数 15 0.15
测验分数 30 0.30
功课成绩 40 0.40

可以通过将权重乘以每个类别的总得分并将所有这些值相加来计算最终得分。最终分数将被转换为最终字母等级。

这意味着您必须计算每个类别的总数。每个类别的总和是一个从0到1的浮点数,代表一个学生相对于最大可能分获得的分数。您将依次处理每个分配类别。

计算考试总分

您将首先计算考试成绩。由于每项考试都有唯一的权重,因此您可以分别计算每项考试的总分。使用for循环最有意义,您可以在以下代码中看到该循环:

n_exams = 3
for n in range(1, n_exams + 1):
    final_data[f"Exam {n} Score"] = (
        final_data[f"Exam {n}"] / final_data[f"Exam {n} - Max Points"]
    )

在此代码中,您设置n_exams等于,3因为在学期中您进行了三门考试。然后,您遍历每项考试,以原始分数除以该考试的最高分来计算分数。

这是四个示例学生的考试数据示例:

网络ID 考试1分数 考试2分数 考试3分数
wxb12345 0.86 0.62 0.90
mxl12345 0.60 0.91 0.93
txj12345 1.00 0.84 0.64
jgf12345 0.72 0.83 0.77

在此表中,每位学生在每项考试中的得分都在0.0到1.0之间。在脚本的最后,您将这些分数乘以权重,以确定最终分数的比例。

计算作业分数

接下来,您需要计算作业分数。每个作业分配的最高分从50到100不等。这意味着有两种方法可以计算作业分数:

  1. 按总分:分别将原始分和最高分相加,然后取比率。
  2. 按平均分数:将每个原始分数除以其各自的最高分,然后取这些比率的总和,然后将合计除以分配的次数。

第一种方法给表现一致的学生更高的分数,第二种方法则奖励那些在工作上表现更好的学生,这些学生的价值更高。为了帮助学生,您将给他们最高的两个分数。

计算这些分数将采取以下步骤:

  1. 收集带有作业数据的列。
  2. 计算总分。
  3. 计算平均分数。
  4. 确定哪个分数更大,并将用于最终分数计算。

首先,您需要收集所有包含作业数据的列。您可以使用DataFrame.filter()以下方法:

homework_scores = final_data.filter(regex=r"^Homework \d\d?$", axis=1)
homework_max_points = final_data.filter(regex=r"^Homework \d\d? -", axis=1)

在此代码中,您使用正则表达式(regex)进行过滤final_data如果列名与正则表达式不匹配,则该列将不会包含在结果DataFrame中。

您传递给的另一个参数DataFrame.filter()axisDataFrame的许多方法可以按行或按列操作,并且您可以使用axis参数在两种方法之间切换使用默认参数axis=0,pandas将在索引中查找与您传递的正则表达式匹配的行。由于您想查找所有与正则表达式匹配的,因此可以通过axis=1

现在,您已经从DataFrame中收集了需要的列,您可以使用它们进行计算了。首先,您将两个值独立求和,然后将它们除以计算总功课成绩:

sum_of_hw_scores = homework_scores.sum(axis=1)
sum_of_hw_max = homework_max_points.sum(axis=1)
final_data["Total Homework"] = sum_of_hw_scores / sum_of_hw_max

在此代码中,使用DataFrame.sum()并传递axis参数。默认情况下,.sum()将累加每列中所有行的值。但是,您需要每一行所有列的总和,因为每一行代表一个学生。axis=1论点告诉pandas就是这样做。

然后你在分配新的列final_data称为Total Homework两个和的比值。

这是四个示例学生的计算结果示例:

网络ID 作业分数总和 最高分数总和 总功课
wxb12345 598 740 0.808108
mxl12345 612 740 0.827027
txj12345 581 740 0.785135
jgf12345 570 740 0.770270

在此表中,您可以查看每个学生的作业分数总和,最大分数总和以及总作业分数。

另一种计算方法是将每个作业分数除以其最高分数,将这些值相加,然后将总数除以作业数量。为此,您可以使用for循环并遍历每一列。但是,pandas可以提高效率,因为它会匹配列标签和索引标签,并且仅对匹配的标签执行数学运算。

为此,您需要更改的列名称homework_max_points以匹配中的名称homework_scores您可以使用DataFrame.set_axis()

hw_max_renamed = homework_max_points.set_axis(homework_scores.columns, axis=1)

在此代码中,您将创建一个新的DataFrame,hw_max_renamed并将这些列设置axis为与中的列相同的名称homework_scores现在,您可以使用此DataFrame进行更多计算:

average_hw_scores = (homework_scores / hw_max_renamed).sum(axis=1)

在此代码中,您可以average_hw_scores通过将每个作业分数除以其各自的最高分来计算。然后,使用DataFrame.sum()和参数将每行中所有作业的比率加在一起axis=1

由于每个单独作业的最大值为1.0,因此该总和可取的最大值将等于作业总数。但是,您需要一个从0到1缩放的数字才能计入最终成绩。

这意味着您需要除以average_hw_scores分配数量,这可以通过以下代码完成:

final_data["Average Homework"] = average_hw_scores / homework_scores.shape[1]

在此代码中,您用于DataFrame.shape从获取任务数homework_scores像NumPy数组一样,DataFrame.shape返回的元组(n_rows, n_columns)从元组中获取第二个值将为您提供中的列数homework_scores,该数量等于分配的数量。

然后,将除法结果分配给final_data名为的新列Average Homework

这是四个示例学生的示例计算结果:

网络ID 平均家庭作业分数之和 平均作业
wxb12345 7.99405 0.799405
mxl12345 8.18944 0.818944
txj12345 7.85940 0.785940
jgf12345 7.65710 0.765710

在此表中,请注意,范围Sum of Average Homework Scores可以从0到10,但Average Homework列从0到1。第二列将用于与Total Homework下一个比较

现在您已经计算了两个作业分数,您可以将最大值用于最终成绩计算:

final_data["Homework Score"] = final_data[
    ["Total Homework", "Average Homework"]
].max(axis=1)

在此代码中,选择刚创建的两列Total HomeworkAverage Homework,然后将最大值分配给名为的新列Homework Score请注意,您为每个学生取的最大值axis=1

这是四个示例学生的计算结果样本:

网络ID 总功课 平均作业 功课成绩
wxb12345 0.808108 0.799405 0.808108
mxl12345 0.827027 0.818944 0.827027
txj12345 0.785135 0.785940 0.785940
jgf12345 0.770270 0.765710 0.770270

在这个表中,你可以比较Total HomeworkAverage Homework和最终Homework Score列。您会看到Homework Score总是反映Total Homework的较大值Average Homework

分数

接下来,您需要计算测验分数。测验也有不同数量的最大分数,因此您需要执行与作业相同的过程。唯一的区别是未在测验数据表中指定每个测验的最高成绩,因此您需要创建一个pandas系列来保存该信息:

quiz_scores = final_data.filter(regex=r"^Quiz \d$", axis=1)
quiz_max_points = pd.Series(
    {"Quiz 1": 11, "Quiz 2": 15, "Quiz 3": 17, "Quiz 4": 14, "Quiz 5": 12}
)

sum_of_quiz_scores = quiz_scores.sum(axis=1)
sum_of_quiz_max = quiz_max_points.sum()
final_data["Total Quizzes"] = sum_of_quiz_scores / sum_of_quiz_max

average_quiz_scores = (quiz_scores / quiz_max_points).sum(axis=1)
final_data["Average Quizzes"] = average_quiz_scores / quiz_scores.shape[1]

final_data["Quiz Score"] = final_data[
    ["Total Quizzes", "Average Quizzes"]
].max(axis=1)

大部分代码与上一节中的作业代码非常相似。从家庭作业情况的主要区别是,您创建了一个pandas系列为quiz_max_points使用字典作为输入。词典的键成为索引标签,词典值成为系列值。

由于中的索引标签quiz_max_points与的名称相同quiz_scores,因此您无需使用DataFrame.set_axis()测验。pandas还广播了Series的形状,使其与DataFrame匹配。

这是测验的计算结果示例:

网络ID 总测验 平均测验 测验分数
wxb12345 0.608696 0.602139 0.608696
mxl12345 0.681159 0.682149 0.682149
txj12345 0.594203 0.585399 0.594203
jgf12345 0.608696 0.615286 0.615286

在此表中,Quiz Score总是期望Total Quizzes的较大值Average Quizzes

计算字母等级

现在,您已经完成了最终成绩的所有必需计算。您的考试,家庭作业和测验的分数都在0到1之间缩放。接下来,您需要将每个分数乘以其权重以确定最终成绩。然后,您可以将该值映射到字母等级A到F的刻度上。

与最高测验分数类似,您将使用pandas系列存储权重。这样,您可以final_data自动乘以正确的列使用以下代码创建权重:

weightings = pd.Series(
    {
        "Exam 1 Score": 0.05,
        "Exam 2 Score": 0.1,
        "Exam 3 Score": 0.15,
        "Quiz Score": 0.30,
        "Homework Score": 0.4,
    }
)

在这段代码中,您为类的每个组件赋予了权重。正如您先前所看到的那样,该课程的总成绩Exam 1为5%,Exam 210%,Exam 315%,测验30%和Homework40%。

接下来,您可以将这些百分比与之前计算出的分数结合起来,以确定最终分数:

final_data["Final Score"] = (final_data[weightings.index] * weightings).sum(
    axis=1
)
final_data["Ceiling Score"] = np.ceil(final_data["Final Score"] * 100)

在此代码中,选择final_data与中的索引同名的列weightings您需要执行此操作,因为其中的某些其他列final_data具有类型str,因此,TypeError如果您尝试将weightings所有乘以,pandas将引发a final_data

接下来,为每个学生取这些列的总和,DataFrame.sum(axis=1)并将其结果分配给名为的新列Final Score此列中每个学生的值是0到1之间的浮点数。

最后,作为您真正的好老师,您将提高每个学生的成绩。您乘每个学生的Final Score通过100把它的规模从0到100,然后使用numpy.ceil()到每个分数四舍五入到下一个最高的整数。您将此值分配给名为的新列Ceiling Score

注意:您必须添加import numpy as np到脚本顶部才能使用np.ceil()

这是四个示例学生的这些列的示例计算结果:

网络ID 最终分数 最高分数
wxb12345 0.745852 75
mxl12345 0.795956 80
txj12345 0.722637 73
jgf12345 0.727194 73

最后要做的是将每个学生的最高分数映射到字母等级上。在您的学校,您可以使用以下字母成绩:

  • :90分以上
  • B:得分在80到90之间
  • C:得分在70到80之间
  • D:得分在60到70之间
  • F:分数低于60

由于每个字母等级都必须映射到一定范围的分数,因此您不能轻松地仅使用词典进行映射。幸运的是,pandas具有Series.map(),它允许您将任意函数应用于系列中的值。如果您使用与字母等级不同的等级比例,则可以执行类似的操作。

您可以通过以下方式编写适当的函数:

grades = {
    90: "A",
    80: "B",
    70: "C",
    60: "D",
    0: "F",
}

def grade_mapping(value):
    for key, letter in grades.items():
        if value >= key:
            return letter

在此代码中,您将创建一个字典,用于存储每个字母等级的下限和字母之间的映射。然后,您定义grade_mapping(),它以最高分系列的一行的值作为参数。与字典中的grades相比value您可以遍历中的项key如果value大于key,则学生将落在该括号中,并且您将返回相应的字母等级。

注意:仅当成绩以降序排列并且依赖于维护字典的顺序时,此功能才起作用。如果您使用的Python版本早于3.6,则需要使用OrderedDict

使用grade_mapping()defined,您可以Series.map()用来查找字母等级:

letter_grades = final_data["Ceiling Score"].map(grade_mapping)
final_data["Final Grade"] = pd.Categorical(
    letter_grades, categories=grades.values(), ordered=True
)

在此代码中,您将letter_grades通过映射grade_mapping()到的Ceiling Score列来创建一个新的系列final_data由于字母等级有五个选择,因此将其作为分类数据类型是有意义的。将分数映射到字母后,您可以使用pandasCategorical创建分类列

要创建分类列,请传递字母等级以及两个关键字参数:

  1. categories从传递值grades中的值grades是该类中可能的字母等级。
  2. ordered被传递True给大pandas以说明类别是有序的。如果您想对该列进行排序,这将对以后有所帮助。

您创建的分类列已分配给final_data名为的新列Final Grade

这是四个示例学生的最终成绩:

网络ID 最终分数 最高分数 期末成绩
wxb12345 0.745852 75 C
mxl12345 0.795956 80
txj12345 0.722637 73 C
jgf12345 0.727194 73 C

在这四个示例学生中,一个人得到B,三个人得到C,这与他们的最高分数和您创建的字母等级映射相匹配。

分组数据

现在,您已经计算出每个学生的成绩,您可能需要将其放入学生管理系统中。这个学期,您正在教同一个课程的几个部分,如Section名册表中列所示。

要将成绩纳入学生管理系统,您需要将学生分为每个部分,并按其姓氏对其进行排序。幸运的是,pandas也涵盖了这里。

pandas具有在DataFrames中对数据进行分组和排序的强大功能。您需要按学生的部门编号对数据进行分组,并按其姓名对分组的结果进行排序。您可以使用以下代码进行操作:

for section, table in final_data.groupby("Section"):
    section_file = DATA_FOLDER / f"Section {section} Grades.csv"
    num_students = table.shape[0]
    print(
        f"In Section {section} there are {num_students} students saved to "
        f"file {section_file}."
    )
    table.sort_values(by=["Last Name", "First Name"]).to_csv(section_file)

在此代码中,使用DataFrame.groupby()onfinal_dataSectionDataFrame.sort_values()进行分组并对分组的结果进行排序。最后,您将排序后的数据保存到CSV文件中,以上传到学生管理系统。这样,您就可以完成本学期的成绩,并且可以放松一下!

绘制摘要统计

但是,在夏天挂起白板笔之前,您可能希望了解有关该课程总体表现的更多信息。使用pandas和Matplotlib,您可以绘制该类的一些摘要统计信息。

首先,您可能希望查看班级中字母等级的分布。您可以使用以下代码进行操作:

grade_counts = final_data["Final Grade"].value_counts().sort_index()
grade_counts.plot.bar()
plt.show()

在此代码,您使用Series.value_counts()Final Gradefinal_data来计算有多少每个字母的出现。默认情况下,值计数从最大到最小排序,但是按字母级顺序查看它们会更有用。Series.sort_index()可以按照定义Categorical时指定的顺序对成绩进行排序

然后,您可以利用pandas使用Matplotlib的能力,并使用生成等级计数的条形图DataFrame.plot.bar()由于这是一个脚本,因此您需要告诉Matplotlib使用来显示绘图plt.show(),这将打开一个交互式图形窗口。

注意:您需要import matplotlib.pyplot as plt在脚本顶部添加,以使其正常工作。

您的图应类似于下图:

字母等级直方图

此图中条形的高度表示水平轴上显示的每个字母等级的学生人数。您的大多数学生都获得C字母成绩。

接下来,您可能需要查看学生的数字得分的直方图。pandas可以使用MatplotlibDataFrame.plot.hist()来自动执行以下操作:

final_data["Final Score"].plot.hist(bins=20, label="Histogram")

在此代码中,DataFrame.plot.hist()可以绘制最终得分的直方图。完成绘制后,所有关键字参数都将传递给Matplotlib。

直方图是估计数据分布的一种方法,但是您可能也对更复杂的方法感兴趣。pandas可以使用SciPy库使用来计算内核密度估计DataFrame.plot.density()您还可以猜测数据将是正态分布的,并手动计算正态分布,其均值和标准差均与数据相同。您可以尝试以下代码以查看其工作方式:

final_data["Final Score"].plot.density(
    linewidth=4, label="Kernel Density Estimate"
)

final_mean = final_data["Final Score"].mean()
final_std = final_data["Final Score"].std()
x = np.linspace(final_mean - 5 * final_std, final_mean + 5 * final_std, 200)
normal_dist = scipy.stats.norm.pdf(x, loc=final_mean, scale=final_std)
plt.plot(x, normal_dist, label="Normal Distribution", linewidth=4)
plt.legend()
plt.show()

在此代码中,您首先使用它DataFrame.plot.density()来绘制数据的内核密度估计值。您可以调整图的线宽和标签以使其更易于查看。

接下来,您可以Final Score使用DataFrame.mean()计算数据的平均值和标准差DataFrame.std()您可以使用np.linspace()生成一组x值,从-5+5远离平均值的标准差。然后,normal_dist通过插入标准正态分布的公式来计算正态分布。

注意:您需要import scipy.stats在脚本顶部添加,以使其正常工作。

最后,绘制xvsnormal_dist并调整线宽并添加标签。显示图后,您将获得如下所示的结果:

带有PDF估计的数值级直方图

在此图中,垂直轴显示了特定容器中等级的密度。峰值出现在0.78的附近。核密度估计值和正态分布都可以很好地匹配数据。

结论

现在,您知道了如何使用pandas构建成绩簿脚本,以便可以停止使用电子表格软件。这将帮助您避免错误,并在将来更快地计算您的最终成绩。

在本教程中,您学习了:

  • 如何数据加载清理合并到pandas DataFrames中
  • 如何使用DataFrames和Series计算
  • 如何将值从一组映射到另一组
  • 如何使用Pandas和Matplotlib绘制汇总统计信息

此外,您还了解了如何对数据进行分组和保存文件以上传到学生管理系统。现在,您可以为下一学期创建pandas成绩簿了!

单击下面的链接以下载此pandas项目的代码,并了解如何在不使用电子表格的情况下构建成绩簿:

飞码网-免费源码博客分享网站 爱上飞码网—https://www.codefrees.com— 飞码网-matlab-python-C++ 爱上飞码网—https://www.codefrees.com— 飞码网-免费源码博客分享网站
赞 ()
内容页底部广告位3
留言与评论(共有 0 条评论)
   
验证码: