检查上方的名称行是否相同并添加条件

2 投票
3 回答
46 浏览
提问于 2025-04-13 12:58

我最近开始学习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 个回答

0

“我想检查上面那一行是不是同一个名字,但培训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
1

如果你刚开始学习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])}")
4

通常来说,你应该尽量避免逐行遍历数据框,因为这样效率不高。

如果你想知道哪个名字对应哪个培训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]

撰写回答