Como você substitui valores duplicados por várias seqüências exclusivas no Pandas?

8
import pandas as pd
import numpy as np
data = {'Name':['Tom', 'Tom', 'Jack', 'Terry'], 'Age':[20, 21, 19, 18]} 
df = pd.DataFrame(data)

Digamos que eu tenho um quadro de dados que se parece com isso. Estou tentando descobrir como verificar a coluna Nome quanto ao valor 'Tom' e, se a encontrar pela primeira vez, substitua-a pelo valor 'FirstTom' e, na segunda vez que aparecer, substitua-a pelo valor 'SecondTom' . Como você consegue isso? Eu usei o método de substituição antes, mas apenas para substituir todos os Toms por um único valor. Não quero adicionar um 1 no final do valor, mas altere completamente a string para outra coisa.

Editar:

Se o df se parecer mais com isso abaixo, como verificaríamos o Tom na primeira e na segunda coluna e depois substituiríamos a primeira instância pelo FirstTom e a segunda instância pelo SecondTom

data = {'Name':['Tom', 'Jerry', 'Jack', 'Terry'], 'OtherName':[Tom, John, Bob,Steve]}

Logan0015
fonte

Respostas:

9

Basta adicionar as soluções existentes inflectpara criar um dicionário dinâmico.

import inflect
p = inflect.engine()

df['Name'] += df.groupby('Name').cumcount().add(1).map(p.ordinal).radd('_')
print(df)

        Name  Age
0    Tom_1st   20
1    Tom_2nd   21
2   Jack_1st   19
3  Terry_1st   18
anky
fonte
7

Nós podemos fazer cumcount

df.Name=df.Name+df.groupby('Name').cumcount().astype(str)
df
     Name  Age
0    Tom0   20
1    Tom1   21
2   Jack0   19
3  Terry0   18

Atualizar

suf = lambda n: "%d%s"%(n,{1:"st",2:"nd",3:"rd"}.get(n if n<20 else n%10,"th"))
g=df.groupby('Name')


df.Name=df.Name.radd(g.cumcount().add(1).map(suf).mask(g.Name.transform('count')==1,''))
df
     Name  Age
0  1stTom   20
1  2ndTom   21
2    Jack   19
3   Terry   18

Atualização 2 para a coluna

suf = lambda n: "%d%s"%(n,{1:"st",2:"nd",3:"rd"}.get(n if n<20 else n%10,"th"))

g=s.groupby([s.index.get_level_values(0),s])
s=s.radd(g.cumcount().add(1).map(suf).mask(g.transform('count')==1,''))
s=s.unstack()
     Name OtherName
0  1stTom    2ndTom
1   Jerry      John
2    Jack       Bob
3   Terry     Steve
YOBEN_S
fonte
1
OP needI don't want to add a 1 on the end of the value
jezrael 28/01
Isso é ótimo, obrigado. Agora, o que acontece se houver uma segunda coluna de nomes e, em vez de verificar os valores verticalmente, ele procura o mesmo nome horizontalmente?
Logan0015 28/01
1
@ Logan0015L você pode fazer df.groupby (['Name1', 'Name2']). Cumcount ()
YOBEN_S
@jezrael No meu entender, se não pudermos construir a corda de 1 a .... enésimo, acho melhor manter o número no nome
YOBEN_S
Isso poderia ser agrupado pela linha em vez da coluna?
Logan0015 28/01
7

EDIT: Para contagem duplicada por linhas, use:

df = pd.DataFrame(data = {'Name':['Tom', 'Jerry', 'Jack', 'Terry'], 
                          'OtherName':['Tom', 'John', 'Bob','Steve'],
                          'Age':[20, 21, 19, 18]})

print (df)
    Name OtherName  Age
0    Tom       Tom   20
1  Jerry      John   21
2   Jack       Bob   19
3  Terry     Steve   18

import inflect
p = inflect.engine()

#map by function for dynamic counter
f = lambda i: p.number_to_words(p.ordinal(i))
#columns filled by names
cols = ['Name','OtherName']
#reshaped to MultiIndex Series
s = df[cols].stack()
#counter per groups
count = s.groupby([s.index.get_level_values(0),s]).cumcount().add(1)
#mask for filter duplicates
mask = s.reset_index().duplicated(['level_0',0], keep=False).values
#filter only duplicates and map, reshape back and add to original data
df[cols] = count[mask].map(f).unstack().add(df[cols], fill_value='')
print (df)
       Name  OtherName  Age
0  firstTom  secondTom   20
1     Jerry       John   21
2      Jack        Bob   19
3     Terry      Steve   18

Use GroupBy.cumcountcom Series.map, mas apenas para valores duplicados Series.duplicated:

data = {'Name':['Tom', 'Tom', 'Jack', 'Terry'], 'Age':[20, 21, 19, 18]} 
df = pd.DataFrame(data)

nth = {
0: "First",
1: "Second",
2: "Third",
3: "Fourth"
}

mask = df.Name.duplicated(keep=False)
df.loc[mask, 'Name'] = df[mask].groupby('Name').cumcount().map(nth) + df.loc[mask, 'Name']
print (df)
        Name  Age
0   FirstTom   20
1  SecondTom   21
2       Jack   19
3      Terry   18

O dicionário dinâmico deve ser como:

import inflect
p = inflect.engine()

mask = df.Name.duplicated(keep=False)
f = lambda i: p.number_to_words(p.ordinal(i))
df.loc[mask, 'Name'] = df[mask].groupby('Name').cumcount().add(1).map(f) + df.loc[mask, 'Name']
print (df)

        Name  Age
0   firstTom   20
1  secondTom   21
2       Jack   19
3      Terry   18
jezrael
fonte
este é um uso muito liso do mapa e da contagem, bom. talvez adicione uma etapa para mostrar o número possível de contagens cumulativas e criar um dicionário dinamicamente?
Datanovice 28/01
5

transform

nth = ['First', 'Second', 'Third', 'Fourth']

def prefix(d):
    n = len(d)
    if n > 1:
        return d.radd([nth[i] for i in range(n)])
    else:
        return d

df.assign(Name=df.groupby('Name').Name.transform(prefix))

          Name  Age
0     FirstTom   20
1    SecondTom   21
2         Jack   19
3        Terry   18
4   FirstSteve   17
5  SecondSteve   16
6   ThirdSteve   15
piRSquared
fonte