如何在Python pandas中生成8位唯一标识符以替换现有标识符

6
假设我有以下简单的数据框。但实际上,我有数十万行像这样的数据。
df
ID              Sales
倀굖곾ꆹ譋῾理     100
倀굖곾ꆹ         50
倀굖곾ꆹ譋῾理     70
곾ꆹ텊躥㫆        60

我的想法是将中文数字替换为类似下面随机生成的8位数字。

ID              Sales
13434535        100
67894335         50
13434535         70
10986467         60

数字是随机生成的,但它们应该保持唯一性。例如,第0行和第2行是相同的,当它被替换为随机生成的唯一ID时,应该仍然相同。
有人可以在Python pandas中提供帮助吗?任何已经完成的解决方案也欢迎。
4个回答

11
主要方法是使用 Series.map() 在“ID”上分配新值。

用于将系列中的每个值替换为另一个值,该值可以来自函数、dictSeries

这正是您要寻找的。
以下是生成新ID的一些选项:

1. 以所需方式生成随机生成的8位整数

您可以首先创建一个包含数据框中每个唯一ID的随机生成的8位整数映射。然后在“ID”上使用Series.map()分配新值回去。我已经包括了一个while循环,以确保生成的ID是唯一的。
import random

original_ids = df['ID'].unique()
while True:
    new_ids = {id_: random.randint(10_000_000, 99_999_999) for id_ in original_ids}
    if len(set(new_ids.values())) == len(original_ids):
        # all the generated id's were unique
        break
    # otherwise this will repeat until they are

df['ID'] = df['ID'].map(new_ids)

输出:

         ID  Sales
0  91154173    100
1  27127403     50
2  91154173     70
3  55892778     60

编辑和警告:原始ID是中文字符,长度已经为8。肯定有超过10个中文字符,因此如果使用错误的原始ID组合,则可能无法为新集合创建足够唯一的8位ID。除非受到内存限制,否则建议使用16-24位数字。或者更好的选择是...

2. 使用UUIDs。[理想情况]

您仍然可以使用ID的“整数”版本而不是十六进制版本。这样做的额外好处是不需要检查唯一性:

import uuid

original_ids = df['ID'].unique()
new_ids = {cid: uuid.uuid4().int for cid in original_ids}
df['ID'] = df['ID'].map(new_ids)

(如果您接受十六进制 ID,请将上面的 uuid.uuid4().int 更改为 uuid.uuid4().hex。)

输出:

                                        ID  Sales
0   10302456644733067873760508402841674050    100
1   99013251285361656191123600060539725783     50
2   10302456644733067873760508402841674050     70
3  112767087159616563475161054356643068804     60

2.B. 从UUID中获取较小的数字

如果上面生成的ID太长,您可以截断它,但存在一些风险。在此,我仅使用前16个十六进制字符并将其转换为int。您可能将其放入与选项1相同的唯一性循环检查中。

import uuid

original_ids = df['ID'].unique()
DIGITS = 16  # number of hex digits of the UUID to use
new_ids = {cid: int(uuid.uuid4().hex[:DIGITS], base=16) for cid in original_ids}
df['ID'] = df['ID'].map(new_ids)

输出:

                     ID  Sales
0  14173925717660158959    100
1  10599965012234224109     50
2  14173925717660158959     70
3  13414338319624454663     60

3. 基于实际值创建映射:

这组选项具有以下优点:

  • 不需要唯一性检查,因为它是基于原始ID确定性生成的,
    • 因此相同的原始ID将生成相同的新ID
  • 不需要提前创建映射表

3.A. CRC32

(与上述2.B选项相比,更容易发现不同ID之间的冲突。)

import zlib

df['ID'] = df['ID'].map(lambda cid: zlib.crc32(bytes(cid, 'utf-8')))

输出:

           ID  Sales
0  2083453980    100
1  1445801542     50
2  2083453980     70
3   708870156     60

3.B. 使用Python内置的hash()函数对原始ID进行哈希[我首选的方法]

  • 可以在一行代码中完成,无需导入任何模块
  • 对于不同的ID生成不会发生冲突,具有合理的安全性
df['ID'] = df['ID'].map(hash)

输出:

                    ID  Sales
0  4663892623205934004    100
1  1324266143210735079     50
2  4663892623205934004     70
3  6251873913398988390     60

3.C. MD5Sum或hashlib中的任何内容

由于ID预计很小(8个字符),即使使用MD5,碰撞的概率也非常低。

import hashlib

DIGITS = 16  # number of hex digits of the hash to use
df['ID'] = df['ID'].str.encode('utf-8').map(lambda x: int(hashlib.md5(x).hexdigest()[:DIGITS], base=16))

输出:

                     ID  Sales
0  17469287633857111608    100
1   4297816388092454656     50
2  17469287633857111608     70
3  11434864915351595420     60

由于数据的重要性,我们不希望出现错误,因此最好控制生成的随机ID的唯一性。如果有成千上万行数据,randint() 生成两个相同的ID的情况可能很少发生。 - Arty
解决方案很好,但映射ID不唯一的概率非常低。 - tino
@Arty 很好的建议,我会更新答案。 - aneroid
@aneroid,我已经检查过了。在映射过程中它无法保持唯一性。能稍微修改一下吗? - Hiwot
1
@Hiwot,请查看您评论之前发布的更新。缺点是,直到找到唯一的ID,您的程序将进入无限循环... - aneroid
显示剩余5条评论

4

虽然不太精通Pandas,但我会使用Numpy + Pandas为您实现解决方案。由于该解决方案使用了快速的Numpy,因此它比纯Python解决方案要快得多,尤其是在有数千行数据时。

在线试用!

import pandas as pd, numpy as np
df = pd.DataFrame([
    ['倀굖곾ꆹ譋῾理', 100],
    ['倀굖곾ꆹ', 50],
    ['倀굖곾ꆹ譋῾理', 70],
    ['곾ꆹ텊躥㫆', 60],
], columns = ['ID', 'Sales'])
u, iv = np.unique(df.ID.values, return_inverse = True)
while True:
    ids = np.random.randint(10 ** 7, 10 ** 8, u.size)
    if np.all(np.unique(ids, return_counts = True)[1] <= 1):
        break
df.ID = ids[iv]
print(df)

输出:

         ID  Sales
0  31043191    100
1  36168634     50
2  31043191     70
3  17162753     60

2
我会:
  • 识别唯一的ID
  • 从np.random中构建一个相同大小的唯一值数组
  • 使用该数组构建转换数据帧
  • 使用merge替换原始ID
可能的代码:
trans = df[['ID']].drop_duplicates()        # unique ID values
n = len(trans)

# np.random.seed(0)       # uncomment for reproducible pseudo random sequences

while True:
    # build a greater array to have a higher chance to get enough unique values
    arr = np.unique(np.random.randint(10000000, 100000000, n + n // 2))
    if len(arr) >= n:
        arr = arr[:n]             # ok keep only the required number
        break

trans['new'] = arr                # ok we have our transformation table

df['ID'] = df.merge(trans, how='left', on='ID')['new']   # done...

使用您提供的示例数据(以及 np.random.seed(0)),得到以下结果:

         ID  Sales
0  12215104    100
1  48712131     50
2  12215104     70
3  70969723     60

根据 @Arty 的评论,np.unique 会返回升序序列。如果不想要升序序列,在使用转换表之前请先打乱它:
...
np.random.shuffle(arr)
trans['new'] = arr
...

顺便提一下,np.random.randint() 不包括上限,因此你的上限应该是 10 ** 8。这与标准 Python 的 random.randint() 不同,后者包括上限。 - Arty
@Arty,我已经根据您的评论编辑了我的帖子。有趣的是,它并没有改变数值... - Serge Ballesta
另外,np.unique() 返回一个排序后的整数列表。不知道这是否是个问题,但在这种情况下,中文 ID 将被逐渐替换为随机增加的 ID。它们并不完全随机,不知道这是否重要。 - Arty
@Arty的,感谢您的评论,因为我之前不知道这一点。我已经编辑了我的帖子... - Serge Ballesta

1
给定一个名为df的数据框,创建一个id列表:
id_list = list(df.ID)

然后导入随机包。
from random import randint
from collections import deque

def idSetToNumber(id_list):
    id_set = deque(set(id_list))
    checked_numbers = []
    while len(id_set)>0:
        #get the id
        id = randint(10000000,99999999)
        #check if the id has been used
        if id not in checked_numbers:
            checked_numbers.append(id)
            id_set.popleft()
    return checked_numbers

这将为您的每个密钥提供一个独特的8位数字列表。然后创建一个字典。
checked_numbers = idSetToNumber(id_list)
name2id = {}
for i in range(len(checked_numbers)):
    name2id[id_list[i]]=checked_numbers[i]

最后一步,使用字典中的ID字段替换所有pandas ID字段。
for i in range(df.shape[0]):
    df.ID[i] = str(name2id[df.ID[i]])
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接