介绍
在 Python 编程中,组织和操作数据集合是一项基本任务。其中一个常见操作是根据特定标准对列表元素进行分组。这个过程将你的数据转换为有组织的类别,使其更易于分析和处理。
在本教程中,你将学习如何使用各种技术有效地对 Python 列表中的元素进行分组。我们将从基本方法开始,逐步引入更强大的内置函数来实现此目的。在本实验(Lab)结束时,你将对在 Python 中分组列表数据的不同方法有一个实际的理解。
在 Python 编程中,组织和操作数据集合是一项基本任务。其中一个常见操作是根据特定标准对列表元素进行分组。这个过程将你的数据转换为有组织的类别,使其更易于分析和处理。
在本教程中,你将学习如何使用各种技术有效地对 Python 列表中的元素进行分组。我们将从基本方法开始,逐步引入更强大的内置函数来实现此目的。在本实验(Lab)结束时,你将对在 Python 中分组列表数据的不同方法有一个实际的理解。
让我们首先了解一下列表分组的含义,以及如何使用 Python 字典实现基本的分组技术。
列表分组是根据特定特征或函数将列表元素组织成类别的过程。例如,你可能希望根据数字是偶数还是奇数来对数字列表进行分组,或者根据单词的首字母对单词列表进行分组。
在 Python 中对列表元素进行分组的最直接方法是使用字典:
让我们创建一个简单的例子,根据数字是偶数还是奇数来对它们进行分组。
首先,让我们创建一个新的 Python 文件来编写我们的代码:
打开 WebIDE 并在 /home/labex/project 目录下创建一个名为 group_numbers.py 的新文件。
将以下代码添加到文件中:
## 使用字典进行基本列表分组
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
## 初始化一个空字典来存储我们的组
even_odd_groups = {"even": [], "odd": []}
## 根据数字是偶数还是奇数进行分组
for num in numbers:
if num % 2 == 0:
even_odd_groups["even"].append(num)
else:
even_odd_groups["odd"].append(num)
## 打印结果组
print("按奇偶分组数字:")
print(f"偶数:{even_odd_groups['even']}")
print(f"奇数:{even_odd_groups['odd']}")
运行脚本以查看结果:
在 WebIDE 中打开一个终端。
执行脚本:
python3 /home/labex/project/group_numbers.py
你应该看到类似如下的输出:
按奇偶分组数字:
偶数: [2, 4, 6, 8, 10]
奇数: [1, 3, 5, 7, 9]
现在,让我们修改我们的脚本,根据数字除以 3 后的余数进行分组:
group_numbers.py 文件中:## 按除以 3 后的余数分组数字
remainder_groups = {0: [], 1: [], 2: []}
for num in numbers:
remainder = num % 3
remainder_groups[remainder].append(num)
print("\n按除以 3 后的余数分组数字:")
for remainder, nums in remainder_groups.items():
print(f"余数为 {remainder} 的数字:{nums}")
保存文件。
再次运行脚本:
python3 /home/labex/project/group_numbers.py
现在你应该看到额外的输出:
按除以 3 后的余数分组数字:
余数为 0 的数字: [3, 6, 9]
余数为 1 的数字: [1, 4, 7, 10]
余数为 2 的数字: [2, 5, 8]
这种使用字典的基本技术提供了一种直接的方法来对列表元素进行分组。但是,随着你的分组需求变得更加复杂,Python 提供了更强大和更有效的方法,我们将在接下来的步骤中进行探讨。
现在你已经了解了分组的基本概念,让我们探索一种更强大的方法,使用内置的 itertools.groupby() 函数。当处理已排序的数据时,此函数特别有用。
itertools 模块中的 groupby() 函数根据一个键函数对可迭代对象中的连续元素进行分组。它返回一个迭代器,该迭代器生成以下配对:
重要提示:groupby() 仅对连续的项目进行分组,因此输入数据通常需要先进行排序。
让我们实现一个例子,看看这在实践中是如何工作的。
在 /home/labex/project 目录下创建一个名为 groupby_example.py 的新文件。
添加以下代码以导入必要的模块:
import itertools
## 示例数据
words = ["apple", "banana", "avocado", "blueberry", "apricot", "blackberry"]
现在,让我们使用 itertools.groupby() 按首字母对单词进行分组:
groupby_example.py 文件中:## 首先,我们需要按将用于分组的键对列表进行排序
## 在这种情况下,每个单词的首字母
words.sort(key=lambda x: x[0])
print("排序后的单词:", words)
## 现在按首字母分组
grouped_words = {}
for first_letter, group in itertools.groupby(words, key=lambda x: x[0]):
grouped_words[first_letter] = list(group)
## 打印结果组
print("\n按首字母分组单词:")
for letter, words_group in grouped_words.items():
print(f"以 '{letter}' 开头的单词:{words_group}")
保存文件。
运行脚本:
python3 /home/labex/project/groupby_example.py
你应该看到类似如下的输出:
排序后的单词: ['apple', 'apricot', 'avocado', 'banana', 'blackberry', 'blueberry']
按首字母分组单词:
以 'a' 开头的单词: ['apple', 'apricot', 'avocado']
以 'b' 开头的单词: ['banana', 'blackberry', 'blueberry']
为了演示在使用 groupby() 时排序的重要性,让我们添加另一个没有排序的例子:
groupby_example.py 文件中:## 示例数据(未排序)
unsorted_words = ["apple", "banana", "avocado", "blueberry", "apricot", "blackberry"]
print("\n--- 首先不排序 ---")
print("原始单词:", unsorted_words)
## 尝试在不排序的情况下分组
unsorted_grouped = {}
for first_letter, group in itertools.groupby(unsorted_words, key=lambda x: x[0]):
unsorted_grouped[first_letter] = list(group)
print("\n在不排序的情况下分组:")
for letter, words_group in unsorted_grouped.items():
print(f"以 '{letter}' 开头的单词:{words_group}")
保存文件。
再次运行脚本:
python3 /home/labex/project/groupby_example.py
在输出中,你会注意到在不排序的情况下分组会产生不同的结果:
--- 首先不排序 ---
原始单词: ['apple', 'banana', 'avocado', 'blueberry', 'apricot', 'blackberry']
在不排序的情况下分组:
以 'a' 开头的单词: ['apple']
以 'b' 开头的单词: ['banana']
以 'a' 开头的单词: ['avocado']
以 'b' 开头的单词: ['blueberry']
以 'a' 开头的单词: ['apricot']
以 'b' 开头的单词: ['blackberry']
注意我们如何有多个具有相同键的组。发生这种情况是因为 groupby() 仅对连续的项目进行分组。当数据未排序时,具有相同键但出现在列表中不同位置的项目将被放置在单独的组中。
itertools.groupby() 函数非常高效,并且是标准库的一部分,这使其成为许多分组任务的强大工具。但是,请记住,它最适合已排序的数据。
在 Python 中,另一个用于分组的强大工具是 collections 模块中的 defaultdict 类。与使用普通字典相比,这种方法提供了更简洁、更有效的方式来对数据进行分组。
defaultdict 是一个字典子类,它会自动初始化缺失键的第一个值。这消除了在将项目添加到字典之前检查键是否存在的需要。对于分组目的而言,这意味着我们可以避免编写条件代码来为新组初始化空列表。
让我们看看 defaultdict 如何简化分组过程。
在 /home/labex/project 目录下创建一个名为 defaultdict_grouping.py 的新文件。
添加以下代码以导入必要的模块并创建一些示例数据:
from collections import defaultdict
## 示例数据 - 包含人员及其年龄的列表
people = [
{"name": "Alice", "age": 25, "city": "New York"},
{"name": "Bob", "age": 30, "city": "Boston"},
{"name": "Charlie", "age": 35, "city": "Chicago"},
{"name": "David", "age": 25, "city": "Denver"},
{"name": "Eve", "age": 30, "city": "Boston"},
{"name": "Frank", "age": 35, "city": "Chicago"},
{"name": "Grace", "age": 25, "city": "New York"}
]
现在,让我们使用 defaultdict 按年龄对人员进行分组:
defaultdict_grouping.py 文件中:## 使用 defaultdict 按年龄对人员进行分组
age_groups = defaultdict(list)
for person in people:
age_groups[person["age"]].append(person["name"])
## 打印结果组
print("按年龄分组人员:")
for age, names in age_groups.items():
print(f"年龄 {age}: {names}")
保存文件。
运行脚本:
python3 /home/labex/project/defaultdict_grouping.py
你应该看到类似如下的输出:
按年龄分组人员:
年龄 25: ['Alice', 'David', 'Grace']
年龄 30: ['Bob', 'Eve']
年龄 35: ['Charlie', 'Frank']
为了理解使用 defaultdict 的优势,让我们将其与普通字典方法进行比较:
defaultdict_grouping.py 文件中:print("\n--- 与普通字典的比较 ---")
## 使用普通字典(传统方法)
regular_dict_groups = {}
for person in people:
age = person["age"]
name = person["name"]
## 需要检查键是否存在
if age not in regular_dict_groups:
regular_dict_groups[age] = []
regular_dict_groups[age].append(name)
print("\n普通字典方法:")
for age, names in regular_dict_groups.items():
print(f"年龄 {age}: {names}")
保存文件。
再次运行脚本:
python3 /home/labex/project/defaultdict_grouping.py
你会注意到这两种方法都产生相同的结果,但是 defaultdict 方法更简洁,并且需要的代码更少。
现在,让我们扩展我们的示例,按城市和年龄对人员进行分组:
defaultdict_grouping.py 文件中:## 按城市然后按年龄分组
city_age_groups = defaultdict(lambda: defaultdict(list))
for person in people:
city = person["city"]
age = person["age"]
name = person["name"]
city_age_groups[city][age].append(name)
print("\n按城市然后按年龄分组人员:")
for city, age_groups in city_age_groups.items():
print(f"\n城市:{city}")
for age, names in age_groups.items():
print(f" 年龄 {age}: {names}")
保存文件。
再次运行脚本:
python3 /home/labex/project/defaultdict_grouping.py
你应该看到额外的输出,类似于:
按城市然后按年龄分组人员:
城市: New York
年龄 25: ['Alice', 'Grace']
城市: Boston
年龄 30: ['Bob', 'Eve']
城市: Chicago
年龄 35: ['Charlie', 'Frank']
城市: Denver
年龄 25: ['David']
这种嵌套的 defaultdict 方法允许使用最少的代码进行更复杂的分组层次结构。当你事先不知道所有组键时,defaultdict 特别有用,因为它会在需要时自动创建新组。
现在你已经了解了几种分组数据的方法,让我们将这些技术应用于解决一个实际问题:分析学生记录数据集。我们将使用不同的分组方法从数据中提取有用的信息。
首先,让我们创建我们的学生记录数据集:
在 /home/labex/project 目录下创建一个名为 student_analysis.py 的新文件。
添加以下代码以设置示例数据:
import itertools
from collections import defaultdict
## 示例学生数据
students = [
{"id": 1, "name": "Emma", "grade": "A", "subject": "Math", "score": 95},
{"id": 2, "name": "Noah", "grade": "B", "subject": "Math", "score": 82},
{"id": 3, "name": "Olivia", "grade": "A", "subject": "Science", "score": 90},
{"id": 4, "name": "Liam", "grade": "C", "subject": "Math", "score": 75},
{"id": 5, "name": "Ava", "grade": "B", "subject": "Science", "score": 88},
{"id": 6, "name": "William", "grade": "A", "subject": "History", "score": 96},
{"id": 7, "name": "Sophia", "grade": "B", "subject": "History", "score": 85},
{"id": 8, "name": "James", "grade": "C", "subject": "Science", "score": 72},
{"id": 9, "name": "Isabella", "grade": "A", "subject": "Math", "score": 91},
{"id": 10, "name": "Benjamin", "grade": "B", "subject": "History", "score": 84}
]
print("学生记录:")
for student in students:
print(f"ID: {student['id']}, 姓名:{student['name']}, 科目:{student['subject']}, 等级:{student['grade']}, 分数:{student['score']}")
让我们分析一下哪些学生正在学习每个科目:
student_analysis.py 文件中:print("\n--- 按科目分组的学生 ---")
## 使用 defaultdict 按科目对学生进行分组
subject_groups = defaultdict(list)
for student in students:
subject_groups[student["subject"]].append(student["name"])
## 按科目打印学生
for subject, names in subject_groups.items():
print(f"{subject}: {names}")
让我们计算每个科目的平均分数:
student_analysis.py 文件中:print("\n--- 按科目的平均分数 ---")
## 计算每个科目的平均分数
subject_scores = defaultdict(list)
for student in students:
subject_scores[student["subject"]].append(student["score"])
## 计算并打印平均值
for subject, scores in subject_scores.items():
average = sum(scores) / len(scores)
print(f"{subject} 平均值:{average:.2f}")
现在让我们使用 itertools.groupby() 分析成绩分布:
student_analysis.py 文件中:print("\n--- 成绩分布 (使用 itertools.groupby) ---")
## 首先按成绩对学生进行排序
sorted_students = sorted(students, key=lambda x: x["grade"])
## 按成绩分组并计数学生
grade_counts = {}
for grade, group in itertools.groupby(sorted_students, key=lambda x: x["grade"]):
grade_counts[grade] = len(list(group))
## 打印成绩分布
for grade, count in grade_counts.items():
print(f"成绩 {grade}: {count} 个学生")
最后,让我们通过结合我们的分组技术来执行更复杂的分析:
student_analysis.py 文件中:print("\n--- 高级分析:按科目的成绩分布 ---")
## 按科目和成绩分组
subject_grade_counts = defaultdict(lambda: defaultdict(int))
for student in students:
subject = student["subject"]
grade = student["grade"]
subject_grade_counts[subject][grade] += 1
## 打印按科目的详细成绩分布
for subject, grades in subject_grade_counts.items():
print(f"\n{subject}:")
for grade, count in grades.items():
print(f" 成绩 {grade}: {count} 个学生")
保存文件。
运行完整的脚本:
python3 /home/labex/project/student_analysis.py
你应该看到对学生数据的全面分析,包括:
此示例演示了如何结合不同的分组技术,以相对简单的代码执行复杂的数据分析。每种方法都有其优点:
defaultdict 非常适合在无需检查键是否存在的情况下进行简单分组itertools.groupby() 对于处理已排序的数据非常有效选择正确的分组技术取决于你的特定需求和数据的结构。
在本教程中,你学习了几种在 Python 中对列表进行分组的有效方法:
基本字典分组:你从使用普通字典的基本方法开始,根据特定标准创建组。
itertools.groupby():你探索了这个内置函数,它有效地对已排序数据中的连续元素进行分组,了解了它的优点和局限性。
collections.defaultdict:你使用了这个方便的字典子类,它会自动处理缺失的键,使你的分组代码更简洁明了。
实际数据分析:你将这些技术应用于分析数据集,了解了它们如何单独使用和组合使用以提取有意义的见解。
这些方法中的每一种都有其优点和理想的用例:
itertools.groupby()defaultdict通过掌握这些分组技术,你已经为你的 Python 编程工具包添加了强大的工具,这将帮助你更有效地组织、分析和操作数据。