【问题标题】:Replacing numerical IDs contained as lists of strings and lists of ints in a Pandas DataFrame替换 Pandas DataFrame 中作为字符串列表和整数列表包含的数字 ID
【发布时间】:2022-02-08 15:33:26
【问题描述】:

为了使数据匿名,我需要用一组不同的新 ID 替换原始 ID,但在替换后仍然有相同的原始 ID 在所有字段中匹配。挑战就是这样做在这个 Pandas DataFrame 中优雅地跨越 4 种不同的 ID 表示。

我有真实世界的数据,其中数字 ID 有 4 种可能的格式:

  1. 在字符串列表中'["38", "15", "42"]'
  2. 在号码列表中[14, 42, 94]
  3. 作为整数42
  4. 作为浮动1.0

这是一个包含所有 4 种数据类型的通用小型 DataFrame。

df = pd.DataFrame([['["38", "15", "42"]', [14, 42, 94], 42, 1.0],\
 ['["8", "28"]', [1, 4], 8, 94.0], ['["12"]', [12], 12, 12.0]],\
 columns = ['CommentsID','AgentID','CaseID','TicketID'])

df
| CommentsID            | AgentID         | CaseID  | TicketID |
| --------------------- | --------------- | ------- | -------- |
| ['38', '15', '42']    | [14, 42, 94]    | 42      | 1.0      |
| ['8', '28']           | [1, 4]          | 8       | 94.0     |
| ['12']                | [12]            | 12      | 12.0     |

为了便于在通用示例中使用,我只是添加 100 来生成“新 ID”列表。 但是,在实际问题中,对应的新ID列表是随机生成的,所以没有全程加100来解决这个问题。

orig_ids = list(range(100))
new_ids = [x + 100 for x in orig_ids]

我想要找到最有效的方法,用这四种数据类型的新 ID 替换数据框中的所有原始 ID。

到目前为止我最好的解决方案是分成三个部分:

  1. 使用replace() 函数处理float 和int 版本(这不会影响列表,即使使用regex=True):
df = df.replace(orig_ids, new_ids)
  1. 对于整数列表,使用远离 Python 的双重 for 循环来匹配 ID 列表上的索引:
def newIDnumbers(datacol):
    newlist = []
    for i in range(len(datacol)):
        numlist = [orig_ids.index(x) for x in df.AgentID[i]]
        newlistrow = []
        for idx in range(len(numlist)):
            newlistrow.append(new_ids[numlist[idx]])
        newlist.append(newlistrow)
    return newlist

df.AgentID = newIDnumbers(df.AgentID)                     
df
  1. 对于字符串列表,构建原始 id 和新 id 的字符串列表,然后使用远离 Python 的双重 for 循环来匹配 ID 列表上的索引:
str_orig_ids = [str(x) for x in orig_ids]
str_new_ids = [str(x) for x in new_ids]

def newIDstrings(datacol):
    newlist = []
    for i in range(len(datacol)):
        numlist = [str_orig_ids.index(x) for x in datacol.str.findall(r'"(\d*)"')[i]]
        newlistrow = []
        for idx in range(len(numlist)):
            newlistrow.append(str_new_ids[numlist[idx]])
        newlist.append(newlistrow)
    return newlist

df.CommentsID = [str(x) for x in newIDstrings(df.CommentsID)]
df       

有没有人有更优雅、计算量更少的方法来实现这个输出?

df
| CommentsID            | AgentID         | CaseID  | TicketID |
| --------------------- | --------------- | ------- | -------- |
| ['138', '115', '142'] | [114, 142, 194] | 142     | 100.0    |
| ['108', '128']        | [101, 104]      | 108     | 194.0    |
| ['112']               | [112]           | 112     | 112.0    |

【问题讨论】:

    标签: python pandas dataframe replace str-replace


    【解决方案1】:

    这是使用重塑和factorize 匿名化 ID 的一种方法:

    from ast import literal_eval
    
    def df_factorize(df):
        idx = df.index
        s = df.reset_index(drop=True).stack()  # stack to ensure consistent
        s[:] = s.factorize()[0]                # factors among all columns
        return s.unstack().set_index(idx)
    
    df2 = (df
       .assign(CommentsID=df['CommentsID'].apply(literal_eval))   # extract as integers
       .explode(['CommentsID', 'AgentID'])                        # lists to rows
       .astype({'CommentsID': int}) # change str to int
       .pipe(df_factorize)                                        # anonymize
       .groupby(level=0)                                          # below to reshape
       .agg({'CommentsID': lambda s: str(list(s.astype(str))),    # to original form
             'AgentID': list,
             'CaseID': 'first',
             'TicketID': 'first',
            })
       .astype(df.dtypes)                                         # original dtypes
    )
    

    输出:

            CommentsID    AgentID  CaseID  TicketID
    0  ['0', '4', '2']  [1, 2, 5]       2       3.0
    1       ['6', '7']     [3, 8]       6       5.0
    2            ['9']        [9]       9       9.0
    

    如果您想要“真正的”匿名化,您可以使用 uuid:

    from uuid import uuid4
    
    def df_uuid(df):
        idx = df.index
        s = df.reset_index(drop=True).stack()
        f = s.unique()
        s = s.map(dict(zip(f, [str(uuid4()) for _ in range(len(f))])))
        return s.unstack().set_index(idx)
    

    输出:

                                                                                                                     CommentsID                                                                                                             AgentID                                CaseID                              TicketID
    0  ['e9fa80ed-58e2-4a96-a8da-0bc0c3a87e14', 'c87ca55a-e0a4-4618-ab5a-2eaf68d5a70f', '7cba72a8-3f2a-42e7-a781-b8ed242ea2ac']  [65f95f56-877e-4a9f-be88-4302fe1f77ea, 7cba72a8-3f2a-42e7-a781-b8ed242ea2ac, 3cf48cd7-e1b0-4c7f-bbeb-41836e309511]  7cba72a8-3f2a-42e7-a781-b8ed242ea2ac  549f1768-39b5-4e74-a8dd-4edbf3bb591f
    1                                          ['6e54d7d6-ae2f-4319-950e-7629128d518a', '8fef2e1c-99d1-4460-b8a4-f4dbbf213c5f']                                        [549f1768-39b5-4e74-a8dd-4edbf3bb591f, 98f82da0-fa9c-447a-943b-b11875736128]  6e54d7d6-ae2f-4319-950e-7629128d518a  3cf48cd7-e1b0-4c7f-bbeb-41836e309511
    2                                                                                  ['3396c9db-1244-4b2d-bb76-14bb0221778f']                                                                              [3396c9db-1244-4b2d-bb76-14bb0221778f]  3396c9db-1244-4b2d-bb76-14bb0221778f  3396c9db-1244-4b2d-bb76-14bb0221778f
    

    【讨论】:

    • 不确定.astype({'col': dtype}) 发生了什么
    • @Either 我不确定你的问题是什么,你想澄清一下吗?
    • .assign(CommentsID=lambda d: d['CommentsID'].astype(int)) 对比上述
    • @Either我可能最初使用了不同的方法,如果您愿意,可以提出edit的建议,我会接受它
    • 这段代码可以满足我的需要。一些有趣的注意事项:1).explode(['CommentsID', 'AgentID']) 中的列表字段必须具有匹配的长度才能工作。 2)如果您想自定义factorize()生成的0-n之外的ID(使其更长,使它们成为字符串),可以将.pipe(df_factorize)行之后的df2代码拆分并添加一行代码修改整个@ 987654333@ 数据集。
    【解决方案2】:

    如果加100就足够了,

    import json
    df['CaseID'] = df['CaseID'] + 100
    df['TicketID'] = df['TicketID'] + 100
    df['AgentID'] = df['AgentID'].apply(lambda x: list(map(lambda y: y+100, x)))
    df['CommentsID'] = df['CommentsID'].apply(lambda x: json.dumps(list(map(lambda y: str(int(y)+100), json.loads(x)))))
    

    对于AgentIDCommentsIDSeries,我们可以使用apply一一进行转换。

    对于AgentID,这更容易,因为它是list的一列,所以我们只需要将list中的每个整数映射到加法即可。

    对于CommentsID,我们需要在开头增加一个步骤,将str 中的列表通过json.loads 转换为python list,最后还需要另外一个步骤将pythonlist 转换回来到str 使用json.dumps

    【讨论】:

    • 用我在示例中定义的df 进行了尝试,但df['CommentsID'] 行没有运行 - 给出错误:TypeError: 'numpy.int64' object is not callable
    猜你喜欢
    • 2016-10-30
    • 1970-01-01
    • 2021-05-29
    • 2022-10-13
    • 2022-09-23
    • 2023-03-31
    • 2021-05-22
    • 2013-08-04
    • 1970-01-01
    相关资源
    最近更新 更多