检查上方的名称行是否相同并添加条件
我最近开始学习Python,想写一个脚本来查看谁参加了什么培训。我的CSV文件里有一个名字列和一个培训ID列。我想检查一下上面那一行的名字是否和当前行的名字相同,但培训ID不同。我发现Pandas不太喜欢用普通的if语句,所以我想知道该怎么解决这个问题。
Name, TrainingID
John,1
John,2
Pete,1
James,1
James,2
James,3
Clint,1
Clint,2
Herb,1
Bob,1
上面是这个CSV文件的简化版本。
我尝试把这个做成一个函数,但它总是输出“无法实现”,因为布尔变量在每一行之间没有切换,只是在第一次找到重复名字的时候就固定了。
有没有办法写一个函数,检查上面那一行的名字是否相同,然后打印出这个人完成了哪些培训呢?
def klaar():
dfCh = pd.read_csv("solitude.csv")
for row in dfCh.iterrows():
booleanM = dfCh.duplicated(subset=['nd']).any()
booleanT = dfCh.duplicated(subset = ['TrainingID']).any()
if booleanM == True and booleanT == True:
print("impossible")
elif booleanM == True and booleanT == False:
print("+ 1 training")
if booleanM == False and booleanT == True:
print("new person")
3 个回答
“我想检查上面那一行是不是同一个名字,但培训ID不同。”
你可以使用 shift
和布尔掩码来实现这个功能:
same_name = df['Name'].eq(df['Name'].shift())
diff_id = df['TrainingID'].ne(df['TrainingID'].shift())
df['flag'] = same_name & diff_id
输出结果:
Name TrainingID flag
0 John 1 False
1 John 2 True
2 Pete 1 False
3 James 1 False
4 James 2 True
5 James 3 True
6 Clint 1 False
7 Clint 2 True
8 Herb 1 False
9 Bob 1 False
中间结果:
Name TrainingID same_name diff_id flag
0 John 1 False True False
1 John 2 True True True
2 Pete 1 False True False
3 James 1 False False False
4 James 2 True True True
5 James 3 True True True
6 Clint 1 False True False
7 Clint 2 True True True
8 Herb 1 False True False
9 Bob 1 False False False
“谁参加了什么培训”
不过,如果你只是想按培训ID把名字分组,简单使用 groupby.agg
就可以了:
df.groupby('TrainingID')['Name'].agg(set)
TrainingID
1 {Clint, James, Herb, Pete, Bob, John}
2 {John, Clint, James}
3 {James}
Name: Name, dtype: object
如果你刚开始学习Python,建议先不要急着学习Pandas,而是先掌握Python本身的基础,了解它处理和操作数据的方式。
下面的内容模仿了e-motta的解决方案,使用字典和Python的标准csv读取器:
- 首先打开要读取的CSV文件,然后用文件处理器f创建一个新的csv.reader。这个读取器会逐行读取文件,每一行返回一个字符串列表。
- 调用next(reader)会让读取器读取并返回第一行,也就是表头,这一行会被丢弃。
- 调用list(reader)会把剩下的所有行读取到一个列表中。
import csv
with open("input.csv", newline="") as f:
reader = csv.reader(f)
next(reader) # skip header
data = list(reader)
print(data)
data其实就是一个字符串列表的列表:
[
["John", "1"],
["John", "2"],
["Pete", "1"],
["James", "1"],
["James", "2"],
["James", "3"],
["Clint", "1"],
["Clint", "2"],
["Herb", "1"],
["Bob", "1"],
]
想要知道每个学生上了多少门课,可以用一个字典来实现,字典的键是学生的名字,值是他们的课程ID:
# Want something like:
# {
# "Alice": ["1", "2" ],
# "Bob": ["1" ],
# "Charlie": ["1", "2", "3"],
# }
students: dict[str, list[str]] = {}
接下来,遍历data中的每一行。检查这个名字是否已经在字典里,如果没有,就把名字加入字典,并设置一个空列表(用来存课程ID),然后把每个课程ID添加到这个学生的名字下:
for row in data:
name, course_id = row
if name not in students:
students[name] = []
students[name].append(course_id)
print(students)
students的样子是:
{
"John": ["1", "2" ],
"Pete": ["1" ],
"James": ["1", "2", "3"],
"Clint": ["1", "2" ],
"Herb": ["1" ],
"Bob": ["1" ],
}
接下来,你可以处理这个字典,按上课数量的多少来打印名字和课程。
首先按照学生上课的数量来排序名字:
def num_courses(name: str) -> int:
return len(students[name])
names = sorted(students, key=num_courses)
print(names)
['Pete', 'Herb', 'Bob', 'John', 'Clint', 'James']
如果你想先按上课数量排序,再按名字排序:
def num_courses_name(name: str) -> tuple[int, str]:
return (len(students[name]), name)
names = sorted(students, key=num_courses_name)
print(names)
['Bob', 'Herb', 'Pete', 'Clint', 'John', 'James']
然后遍历这些名字,从字典中获取他们的课程并打印出来。{x: <10}
的意思是“把名字左对齐,并在右边填充最多10个空格”:
for x in names:
print(f"student: {x:<10} courses: {','.join(students[x])}")
student: Bob courses: 1
student: Herb courses: 1
student: Pete courses: 1
student: Clint courses: 1,2
student: John courses: 1,2
student: James courses: 1,2,3
再次从上到下:
import csv
with open("input.csv", newline="") as f:
reader = csv.reader(f)
next(reader) # skip header
data = list(reader)
students: dict[str, list[str]] = {}
for row in data:
name, course_id = row
if name not in students:
students[name] = []
students[name].append(course_id)
def num_courses_name(name: str) -> tuple[int, str]:
return (len(students[name]), name)
names = sorted(students, key=num_courses_name)
for x in names:
print(f"student: {x:<10} courses: {','.join(students[x])}")
通常来说,你应该尽量避免逐行遍历数据框,因为这样效率不高。
如果你想知道哪个名字对应哪个培训ID:
df.groupby("Name", as_index=False)["TrainingID"].agg(list)
Name TrainingID
0 Bob [1]
1 Clint [1, 2]
2 Herb [1]
3 James [1, 2, 3]
4 John [1, 2]
5 Pete [1]
如果你想知道哪个培训ID和哪个名字有关:
df.groupby("TrainingID", as_index=False)["Name"].agg(list)
TrainingID Name
0 1 [John, Pete, James, Clint, Herb, Bob]
1 2 [John, James, Clint]
2 3 [James]