¿Cómo se reemplazan los valores duplicados con múltiples cadenas únicas en 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 tengo un marco de datos que se ve así. Estoy tratando de descubrir cómo verificar la columna Nombre para el valor 'Tom' y si lo encuentro la primera vez que lo reemplazo con el valor 'FirstTom' y la segunda vez que aparece lo reemplazo con el valor 'SecondTom' . ¿Cómo se logra esto? He usado el método de reemplazo antes, pero solo para reemplazar todos los Toms con un solo valor. No quiero agregar un 1 al final del valor, pero cambiar completamente la cadena a otra cosa.

Editar:

Si el df se parecía más a esto a continuación, ¿cómo verificaríamos a Tom en la primera columna y la segunda columna y luego reemplazaríamos la primera instancia con FirstTom y la segunda instancia con SecondTom?

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

Logan0015
fuente

Respuestas:

9

Simplemente agregando a las soluciones existentes, puede usar inflectpara crear un diccionario 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
fuente
7

Podemos hacer 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

Actualizar

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

Actualización 2 para columna

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
fuente
1
Necesidad de OPI don't want to add a 1 on the end of the value
Israel
Esto es genial, gracias. Ahora, ¿qué pasa si hay una segunda columna de nombres y, en lugar de verificar los valores verticalmente, verifica el mismo nombre horizontalmente?
Logan0015
1
@ Logan0015L puede hacer df.groupby (['Name1', 'Name2']). Cumcount ()
YOBEN_S
@jezrael En mi opinión, si no podemos construir la cadena del 1 al ... enésimo, creo que es mejor mantener el número en el nombre
YOBEN_S
¿Podría esto agruparse por la fila en lugar de la columna?
Logan0015
7

EDITAR: Para el recuento duplicado por filas, 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.cumcountcon Series.map, pero solo para valores duplicados por 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

El diccionario dinámico debería 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
fuente
Este es un uso muy hábil del mapa y la cuenta, uno agradable. ¿quizás agregar un paso para mostrar el número de posibles recuentos acumulativos y construir un diccionario dinámicamente?
Datanovice
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
fuente