如何在两列列表中获取唯一的电子邮件,其中值可以有两个(或更多!)单独的值

How to get unique emails in a two column list where values can have two (or more!) separate values

这是我几周前问过的一个类似问题的转贴,我认为我能够做到这一点,但我的表现会付出巨大的(阅读:无法维持的)代价。那里有一个英雄海报对我有所帮助,我在 his/her 方向重新发布。我的代码:

Donors = pd.read_csv(r"C:\Users\am\Desktop\email parsing\Q1 2021\Donors Q1 2021 R12.csv",
                        usecols=["Email Address"], na_values= True)
Activists = pd.read_csv(r"C:\Users\am\Desktop\email parsing\Q1 2021\Activists Q1 2021 R12.csv",
                        usecols=["Email"], na_filter= True)
Low_Level_Activists = pd.read_csv(r"C:\Users\am\Desktop\email parsing\Q1 2021\Low Level Activists Q1 2021 R12.csv",
                        usecols=["Email"], na_filter= True)
Ambassadors = pd.read_csv(r"C:\Users\am\Desktop\email parsing\Q1 2021\Student Ambassadors Q1 2021.csv",
                        usecols=["Email Address"], na_filter= True)
Volunteers = pd.read_csv(r"C:\Users\am\Desktop\email parsing\Q1 2021\Volunteers Q1 2021 R12.csv",
                        usecols=["Email Address"], na_filter= True)

Donors['Value'] = "Donors"
Activists['Value'] = "Activists"
Low_Level_Activists['Value'] = "Low_Level_Activists"
Ambassadors['Value'] = "Ambassadors"
Volunteers['Value'] = "Volunteers"
Advocates['Value'] = 'Followers'

S1= pd.concat([Donors,Activists,Low_Level_Activists,Ambassadors,Volunteers,Advocates], ignore_index= True)
S1['Handle'] = S1['Email Address'].where(S1['Email Address'].notnull(), S1['Email'])
S1= S1.drop(['Email','Email Address'], axis = 1)
print(S1['Handle'].count()) #checks full count

现在是困难的部分 - 电子邮件地址可以是捐助者和活动家、活动家和志愿者,所有五个价值观等等,但大多数情况下它是一个电子邮件一个值。 我需要获取一个 Dataframe(或列表或字典),我在其中捕获了最早导入的唯一电子邮件地址 class。如果电子邮件是捐助者和志愿者,那么它就是捐助者。如果是 Activist 和 Low Level Activist 那么 Activist 等等。

我正在寻找具有每个值组以及电子邮件的唯一计数的输出。此代码:

pd.Series({value: len(group.Handle.unique()) for value, group in S1.groupby('Value')})

可以解决问题 (h/t Jan W.),但它不会根据最早的“值”压缩电子邮件计数。帮助将不胜感激,因为我已经在这里工作了很多天。此集合中有 1,700 个实例,其中一封电子邮件存在于 167,000 个电子邮件地址中的两个或更多位置。我似乎无法绕过使用 for 循环然后切换回 pandas 系列等。如果我能获得有效的代码再次解决这个问题,将不胜感激!

示例数据(再次 h/t Jan W):

fa = pd.DataFrame([['paul@mail.com', 'Donors'], ['max@mail.com', 'Donors']], columns=['Handle', 'Value'])
fb = pd.DataFrame([['paul@mail.com', 'Activists'], ['annie@mail.com', 'Activists']], columns=['Handle', 'Value'])
S1 = pd.concat([fa, fb])

结合pandas groupbysort_values 方法将使运行 一切变得相当快。下面的代码片段是一个示例实现。

首先生成约 100K 行的随机数据(这有点扭曲但与您的实际问题无关):

from itertools import combinations
import string

import numpy as np
import pandas as pd


emails = [
    f"{''.join(chars)}@{domain}"
    for chars in combinations(string.ascii_lowercase, 4)
    for domain in ["example.com", "domain.org"]
]
roles = [
    "Donors",
    "Activists",
    "Low_Level_Activists",
    "Ambassadors",
    "Ambassadors",
    "Ambassadors",
]

n = 100_000

df = pd.DataFrame(
    {
        "Handle": np.random.choice(emails, size=n),
        "Value": np.random.choice(roles, size=n),
    }
).drop_duplicates()

现在 df 看起来像:

                 Handle                Value
0       agnt@domain.org          Ambassadors
1       fmox@domain.org  Low_Level_Activists
2       cmny@domain.org  Low_Level_Activists
3       fgpq@domain.org          Ambassadors
4      hikw@example.com  Low_Level_Activists
...                 ...                  ...
99993   ejvz@domain.org            Activists
99994   aehr@domain.org          Ambassadors
99995  efjy@example.com          Ambassadors
99997  aeow@example.com          Ambassadors
99999   aetw@domain.org               Donors

[62609 rows x 2 columns]

然后为每封电子邮件获取它所属的第一个角色:

df = df.sort_values("Value").groupby("Handle", as_index=False)["Value"].first()
                 Handle        Value
0       abcd@domain.org    Activists
1      abcd@example.com       Donors
2       abce@domain.org  Ambassadors
3      abce@example.com    Activists
4       abcf@domain.org  Ambassadors
...                 ...          ...
28848  vwyz@example.com    Activists
28849   vxyz@domain.org  Ambassadors
28850  vxyz@example.com  Ambassadors
28851   wxyz@domain.org  Ambassadors
28852  wxyz@example.com    Activists
[28853 rows x 2 columns]

最后计算每个角色的电子邮件数量:

df = df.groupby("Value")["Handle"].nunique()
Value
Activists              12765
Ambassadors            13987
Donors                  1374
Low_Level_Activists      727
Name: Handle, dtype: int64

一旦定义了 df,上面的两个计算在我的笔记本电脑上仅需 运行 ~100ms。