¿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]}

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

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

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

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
​
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.