web-dev-qa-db-fra.com

Q: [Pandas] Comment attribuer efficacement un ID unique à des individus avec plusieurs entrées en fonction du nom en très gros df

Je voudrais prendre un ensemble de données avec un tas de différents individus uniques, chacun avec plusieurs entrées, et attribuer à chaque individu un identifiant unique pour toutes leurs entrées. Voici un exemple de df:

      FirstName LastName  id
0     Tom       Jones     1
1     Tom       Jones     1
2     David     Smith     1
3     Alex      Thompson  1
4     Alex      Thompson  1

Donc, fondamentalement, je veux que toutes les entrées pour Tom Jones aient id = 1, toutes les entrées pour David Smith pour avoir id = 2, toutes les entrées pour Alex Thompson pour avoir id = 3, et ainsi de suite.

J'ai donc déjà une solution, qui est une simple boucle python boucle itérant deux valeurs (une pour id, une pour index) et attribuant à l'individu un id en fonction de leur correspondance avec l'individu précédent:

x = 1
i = 1

while i < len(df_test):
    if (df_test.LastName[i] == df_test.LastName[i-1]) & 
    (df_test.FirstName[i] == df_test.FirstName[i-1]):
        df_test.loc[i, 'id'] = x
        i = i+1
    else:
        x = x+1
        df_test.loc[i, 'id'] = x
        i = i+1

Le problème que je rencontre est que la trame de données a environ 9 millions d'entrées, donc avec cette boucle, cela aurait pris énormément de temps à s'exécuter. Quelqu'un peut-il penser à un moyen plus efficace de le faire? J'ai regardé groupby et multiindexing comme des solutions potentielles, mais je n'ai pas encore trouvé la bonne solution. Merci!

17
Simon Sharp

Vous pouvez joindre le nom et le prénom, les convertir en catégorie, puis obtenir les codes.

Bien sûr, plusieurs personnes portant le même nom auraient le même id.

df = df.assign(id=(df['LastName'] + '_' + df['FirstName']).astype('category').cat.codes)
>>> df
  FirstName  LastName  id
0       Tom     Jones   0
1       Tom     Jones   0
2     David     Smith   1
3      Alex  Thompson   2
4      Alex  Thompson   2
16
Alexander

Cette approche utilise .groupby() et .ngroup() (nouveau dans Pandas 0.20.2) pour créer la colonne id:

df['id'] = df.groupby(['LastName','FirstName']).ngroup()
>>> df

   First    Second  id
0    Tom     Jones   0
1    Tom     Jones   0
2  David     Smith   1
3   Alex  Thompson   2
4   Alex  Thompson   2

J'ai vérifié les horaires et, pour le petit ensemble de données dans cet exemple, la réponse d'Alexandre est plus rapide:

%timeit df.assign(id=(df['LastName'] + '_' + df['FirstName']).astype('category').cat.codes)
1000 loops, best of 3: 848 µs per loop

%timeit df.assign(id=df.groupby(['LastName','FirstName']).ngroup())
1000 loops, best of 3: 1.22 ms per loop

Cependant, pour les cadres de données plus volumineux, l'approche groupby() semble être plus rapide. Pour créer un grand ensemble de données représentatif, j'ai utilisé faker pour créer une trame de données de 5000 noms, puis j'ai concaténé les 2000 premiers noms à cette trame de données pour créer une trame de données avec 7000 noms, dont 2000 étaient des doublons.

import faker
fakenames = faker.Faker()
first = [ fakenames.first_name() for _ in range(5000) ]
last = [ fakenames.last_name() for _ in range(5000) ]
df2 = pd.DataFrame({'FirstName':first, 'LastName':last})
df2 = pd.concat([df2, df2.iloc[:2000]])

L'exécution du timing sur cet ensemble de données plus volumineux donne:

%timeit df2.assign(id=(df2['LastName'] + '_' + df2['FirstName']).astype('category').cat.codes)
100 loops, best of 3: 5.22 ms per loop

%timeit df2.assign(id=df2.groupby(['LastName','FirstName']).ngroup())
100 loops, best of 3: 3.1 ms per loop

Vous souhaiterez peut-être tester les deux approches sur votre ensemble de données pour déterminer celle qui fonctionne le mieux compte tenu de la taille de vos données.

26
Craig

Cette méthode permet de définir le nom de la colonne 'id' avec une variable. De plus, je le trouve un peu plus facile à lire que les méthodes d'assignation ou de groupby.

# Create Dataframe
df = pd.DataFrame(
    {'FirstName': ['Tom','Tom','David','Alex','Alex'],
    'LastName': ['Jones','Jones','Smith','Thompson','Thompson'],
    })

newIdName = 'id'   # Set new name here.

df[newIdName] = (df['LastName'] + '_' + df['FirstName']).astype('category').cat.codes     

Sortie:

>>> df
          FirstName  LastName  id
        0       Tom     Jones   0
        1       Tom     Jones   0
        2     David     Smith   1
        3      Alex  Thompson   2
        4      Alex  Thompson   2
1
DougR