Modifica un valore in base a un altro valore in panda


109

Sto cercando di riprogrammare il mio codice Stata in Python per migliorare la velocità, e sono stato indirizzato nella direzione di PANDAS. Tuttavia, sto avendo difficoltà a capire come elaborare i dati.

Diciamo che voglio iterare su tutti i valori nell'intestazione della colonna "ID". Se quell'ID corrisponde a un numero specifico, voglio modificare due valori corrispondenti FirstName e LastName.

In Stata assomiglia a questo:

replace FirstName = "Matt" if ID==103
replace LastName =  "Jones" if ID==103

Quindi questo sostituisce tutti i valori in FirstName che corrispondono ai valori di ID == 103 a Matt.

In PANDAS, sto provando qualcosa di simile

df = read_csv("test.csv")
for i in df['ID']:
    if i ==103:
          ...

Non sono sicuro di dove andare da qui. Qualche idea?

Risposte:


182

Un'opzione è usare le funzionalità di affettatura e indicizzazione di Python per valutare logicamente i luoghi in cui la tua condizione è contenuta e sovrascrivere i dati lì.

Supponendo che tu possa caricare i tuoi dati direttamente in pandascon, pandas.read_csvil seguente codice potrebbe essere utile per te.

import pandas
df = pandas.read_csv("test.csv")
df.loc[df.ID == 103, 'FirstName'] = "Matt"
df.loc[df.ID == 103, 'LastName'] = "Jones"

Come accennato nei commenti, puoi anche eseguire l'assegnazione a entrambe le colonne in un colpo solo:

df.loc[df.ID == 103, ['FirstName', 'LastName']] = 'Matt', 'Jones'

Nota che avrai bisogno della pandasversione 0.11 o successiva da utilizzare locper le operazioni di assegnazione di sovrascrittura.


Un altro modo per farlo è utilizzare ciò che viene chiamato assegnazione concatenata. Il comportamento di questo è meno stabile e quindi non è considerata la soluzione migliore (è esplicitamente sconsigliata nella documentazione), ma è utile conoscere:

import pandas
df = pandas.read_csv("test.csv")
df['FirstName'][df.ID == 103] = "Matt"
df['LastName'][df.ID == 103] = "Jones"

16
che ne dici di aggiungere anche questo sapore:df.loc[df.ID == 103, ['FirstName', 'LastName']] = 'Matt', 'Jones'
Boud

2
-1 "Un altro modo per farlo è usare ciò che viene chiamato assegnazione concatenata." No. Enfaticamente, no. È utile solo sapere che l'assegnazione concatenata non è affidabile. Non è che sia una soluzione affidabile e non ottimale, la situazione è molto peggiore . Lo hai anche riconosciuto altrove su Stack Overflow . Cerca di evitare di dare l'illusione che l'assegnazione concatenata sia un'opzione praticabile. I primi due metodi che hai fornito erano sufficienti e sono il modo preferito per farlo.
Phillip Cloud

9
Non sono d'accordo. Non capisco perché ti ostini a tentare pedanticamente di affermare che l'assegnazione concatenata non è una via praticabile. Ho riconosciuto che non è considerato il modo preferito. Che cosa vuoi di più. È assurdo agire come se questo non fosse un modo per farlo. In effetti, nel mio sistema in questo momento (versione 0.8), è il modo giusto per farlo. Non mi interessano i tuoi voti positivi se prendi questa posizione. Sentiti libero di segnalare il tuo punto con un voto negativo, ma ho già riflettuto sul tuo punto e non sono d'accordo.
ely

11
Internet è una cosa seria. In ogni caso, EMS, ho apprezzato sapere che l'opzione esiste.
Serpentese

Un problema che potresti incontrare è che il csv ha punti / punti nei nomi delle colonne e le assegnazioni vengono incasinate. Puoi correggere le colonne usando qualcosa del genere: cols = df.columns cols = cols.map (lambda x: x.replace ('.', '_') If isinstance (x, str) else x) df.columns = cols
ski_squaw

37

Puoi usare map, può mappare vales da un dictonairy o anche una funzione personalizzata.

Supponiamo che questo sia il tuo df:

    ID First_Name Last_Name
0  103          a         b
1  104          c         d

Crea i dict:

fnames = {103: "Matt", 104: "Mr"}
lnames = {103: "Jones", 104: "X"}

E mappa:

df['First_Name'] = df['ID'].map(fnames)
df['Last_Name'] = df['ID'].map(lnames)

Il risultato sarà:

    ID First_Name Last_Name
0  103       Matt     Jones
1  104         Mr         X

Oppure usa una funzione personalizzata:

names = {103: ("Matt", "Jones"), 104: ("Mr", "X")}
df['First_Name'] = df['ID'].map(lambda x: names[x][0])

2
Questo non genererà un KeyError se i valori non esistono nel tuo dict?
EdChum

1
La funzione personalizzata funzionerà, le altre funzioneranno comunque. Ma ho pensato che dictfosse stato creato per la mappatura. In caso contrario, è possibile eseguire alcuni controlli / pulizie in base a qualcosa come:df.ID.isin(names.keys())
Rutger Kassies

La funzione personalizzata può essere espansa in qualsiasi funzione (non anonima).
user989762

14

La domanda originale affronta uno specifico caso d'uso ristretto. Per chi necessita di risposte più generiche ecco alcuni esempi:

Creazione di una nuova colonna utilizzando i dati di altre colonne

Dato il dataframe di seguito:

import pandas as pd
import numpy as np

df = pd.DataFrame([['dog', 'hound', 5],
                   ['cat', 'ragdoll', 1]],
                  columns=['animal', 'type', 'age'])

In[1]:
Out[1]:
  animal     type  age
----------------------
0    dog    hound    5
1    cat  ragdoll    1

Di seguito stiamo aggiungendo una nuova descriptioncolonna come concatenazione di altre colonne utilizzando l' +operazione che viene sovrascritta per le serie. La formattazione di stringa fantasia, le stringhe f ecc. Non funzioneranno qui poiché si +applica a valori scalari e non "primitivi":

df['description'] = 'A ' + df.age.astype(str) + ' years old ' \
                    + df.type + ' ' + df.animal

In [2]: df
Out[2]:
  animal     type  age                description
-------------------------------------------------
0    dog    hound    5    A 5 years old hound dog
1    cat  ragdoll    1  A 1 years old ragdoll cat

Otteniamo 1 yearsper il gatto (invece di 1 year) che aggiusteremo di seguito usando i condizionali.

Modifica di una colonna esistente con condizionali

Qui stiamo sostituendo la animalcolonna originale con valori di altre colonne e utilizziamo np.whereper impostare una sottostringa condizionale basata sul valore di age:

# append 's' to 'age' if it's greater than 1
df.animal = df.animal + ", " + df.type + ", " + \
    df.age.astype(str) + " year" + np.where(df.age > 1, 's', '')

In [3]: df
Out[3]:
                 animal     type  age
-------------------------------------
0   dog, hound, 5 years    hound    5
1  cat, ragdoll, 1 year  ragdoll    1

Modifica di più colonne con condizionali

Un approccio più flessibile consiste nel richiamare .apply()un intero dataframe piuttosto che una singola colonna:

def transform_row(r):
    r.animal = 'wild ' + r.type
    r.type = r.animal + ' creature'
    r.age = "{} year{}".format(r.age, r.age > 1 and 's' or '')
    return r

df.apply(transform_row, axis=1)

In[4]:
Out[4]:
         animal            type      age
----------------------------------------
0    wild hound    dog creature  5 years
1  wild ragdoll    cat creature   1 year

Nel codice sopra la transform_row(r)funzione accetta un Seriesoggetto che rappresenta una data riga (indicato da axis=1, il valore predefinito di axis=0fornirà un Seriesoggetto per ogni colonna). Ciò semplifica l'elaborazione poiché possiamo accedere ai valori "primitivi" effettivi nella riga utilizzando i nomi delle colonne e avere visibilità di altre celle nella riga / colonna specificata.


1
Grazie per aver dedicato del tempo a scrivere una risposta così esauriente. Molto apprezzato.
Serpentese

Grazie per questa risposta estremamente utile. Un follow-up: cosa succede se vogliamo modificare una colonna eseguendo calcoli sulla colonna, invece di modificare una stringa? Ad esempio, usando l'esempio sopra, cosa succede se vogliamo moltiplicare la colonna df.age per 7 se df.animal == 'dog'? Grazie!
GbG

1
@GbG: np.whereè probabilmente quello che stai cercando, vedi ad esempio stackoverflow.com/a/42540310/191246 ma è anche possibile che non sarai in grado di adattare la logica in un'operazione scalare, quindi dovresti trasformarla esplicitamente la cella numericamente simile a come è fatta intransform_row
ccpizza

Grazie @ccpizza! Proprio quello che stavo cercando.
GbG

13

Questa domanda potrebbe ancora essere visitata abbastanza spesso da far valere la pena di offrire un'aggiunta alla risposta dell'onorevole Kassies. La dictclasse incorporata può essere sottoclassata in modo che venga restituito un valore predefinito per le chiavi "mancanti". Questo meccanismo funziona bene per i panda. Ma vedi sotto.

In questo modo è possibile evitare errori chiave.

>>> import pandas as pd
>>> data = { 'ID': [ 101, 201, 301, 401 ] }
>>> df = pd.DataFrame(data)
>>> class SurnameMap(dict):
...     def __missing__(self, key):
...         return ''
...     
>>> surnamemap = SurnameMap()
>>> surnamemap[101] = 'Mohanty'
>>> surnamemap[301] = 'Drake'
>>> df['Surname'] = df['ID'].apply(lambda x: surnamemap[x])
>>> df
    ID  Surname
0  101  Mohanty
1  201         
2  301    Drake
3  401         

La stessa cosa può essere eseguita più semplicemente nel modo seguente. L'uso dell'argomento "default" per il getmetodo di un oggetto dict rende non necessario creare una sottoclasse di un dict.

>>> import pandas as pd
>>> data = { 'ID': [ 101, 201, 301, 401 ] }
>>> df = pd.DataFrame(data)
>>> surnamemap = {}
>>> surnamemap[101] = 'Mohanty'
>>> surnamemap[301] = 'Drake'
>>> df['Surname'] = df['ID'].apply(lambda x: surnamemap.get(x, ''))
>>> df
    ID  Surname
0  101  Mohanty
1  201         
2  301    Drake
3  401         

1
questa è di gran lunga la risposta migliore e più semplice che ho visto, con un'eccellente gestione predefinita. Grazie.
Brendan

@Brendan: Oh! Grazie mille.
Bill Bell
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.