python - trying - pandas iloc




Aggiunta di una nuova colonna a DataFrame esistente nei panda Python (14)

Vorrei aggiungere una nuova colonna, 'e', ​​al frame di dati esistente e non modificare nulla nel frame di dati. (La serie ha sempre la stessa lunghezza di un dataframe.)

Suppongo che i valori dell'indice in e corrispondano a quelli in df1 .

Il modo più semplice per iniziare una nuova colonna denominata e , assegnandole i valori della tua serie e :

df['e'] = e.values

assegnare (Pandas 0.16.0+)

A partire da Pandas 0.16.0, è anche possibile utilizzare assign , che assegna nuove colonne a un DataFrame e restituisce un nuovo oggetto (una copia) con tutte le colonne originali oltre a quelle nuove.

df1 = df1.assign(e=e.values)

In base a questo esempio (che include anche il codice sorgente della funzione di assign ), puoi anche includere più di una colonna:

df = pd.DataFrame({'a': [1, 2], 'b': [3, 4]})
>>> df.assign(mean_a=df.a.mean(), mean_b=df.b.mean())
   a  b  mean_a  mean_b
0  1  3     1.5     3.5
1  2  4     1.5     3.5

Nel contesto del tuo esempio:

np.random.seed(0)
df1 = pd.DataFrame(np.random.randn(10, 4), columns=['a', 'b', 'c', 'd'])
mask = df1.applymap(lambda x: x <-0.7)
df1 = df1[-mask.any(axis=1)]
sLength = len(df1['a'])
e = pd.Series(np.random.randn(sLength))

>>> df1
          a         b         c         d
0  1.764052  0.400157  0.978738  2.240893
2 -0.103219  0.410599  0.144044  1.454274
3  0.761038  0.121675  0.443863  0.333674
7  1.532779  1.469359  0.154947  0.378163
9  1.230291  1.202380 -0.387327 -0.302303

>>> e
0   -1.048553
1   -1.420018
2   -1.706270
3    1.950775
4   -0.509652
dtype: float64

df1 = df1.assign(e=e.values)

>>> df1
          a         b         c         d         e
0  1.764052  0.400157  0.978738  2.240893 -1.048553
2 -0.103219  0.410599  0.144044  1.454274 -1.420018
3  0.761038  0.121675  0.443863  0.333674 -1.706270
7  1.532779  1.469359  0.154947  0.378163  1.950775
9  1.230291  1.202380 -0.387327 -0.302303 -0.509652

La descrizione di questa nuova funzione quando è stata introdotta per la prima volta può essere trovata here .

Ho il seguente DataFrame indicizzato con colonne e numeri non continui di righe:

          a         b         c         d
2  0.671399  0.101208 -0.181532  0.241273
3  0.446172 -0.243316  0.051767  1.577318
5  0.614758  0.075793 -0.451460 -0.012493

Vorrei aggiungere una nuova colonna, 'e' , al frame di dati esistente e non voglio modificare nulla nel frame di dati (cioè, la nuova colonna ha sempre la stessa lunghezza del DataFrame).

0   -0.335485
1   -1.166658
2   -0.385571
dtype: float64

Ho provato diverse versioni di join , append , merge , ma non ho ottenuto il risultato che volevo, solo gli errori al massimo. Come posso aggiungere la colonna e all'esempio sopra?


Assegnazione di colonne super semplice

Un dataframe panda è implementato come un ordine ordinato di colonne.

Ciò significa che __getitem__ [] non può essere utilizzato solo per ottenere una determinata colonna, ma __setitem__ [] = può essere utilizzato per assegnare una nuova colonna.

Ad esempio, questo dataframe può avere una colonna aggiunta semplicemente usando l'accessor []

    size      name color
0    big      rose   red
1  small    violet  blue
2  small     tulip   red
3  small  harebell  blue

df['protected'] = ['no', 'no', 'no', 'yes']

    size      name color protected
0    big      rose   red        no
1  small    violet  blue        no
2  small     tulip   red        no
3  small  harebell  blue       yes

Nota che funziona anche se l'indice del dataframe è disattivato.

df.index = [3,2,1,0]
df['protected'] = ['no', 'no', 'no', 'yes']
    size      name color protected
3    big      rose   red        no
2  small    violet  blue        no
1  small     tulip   red        no
0  small  harebell  blue       yes

[] = è la strada da percorrere, ma attenzione!

Tuttavia, se si ha un pd.Series e si prova ad assegnarlo a un dataframe in cui gli indici sono spenti, si verificheranno dei problemi. Vedi esempio:

df['protected'] = pd.Series(['no', 'no', 'no', 'yes'])
    size      name color protected
3    big      rose   red       yes
2  small    violet  blue        no
1  small     tulip   red        no
0  small  harebell  blue        no

Questo perché un pd.Series per impostazione predefinita ha un indice enumerato da 0 a n. E il panda [] = metodo cerca di essere "intelligente"

Cosa sta succedendo davvero.

Quando si utilizza il metodo [] = , i panda eseguono tranquillamente un outer join o outer merge usando l'indice del dataframe di sinistra e l'indice della serie di destra. df['column'] = series

Nota a margine

Questo causa rapidamente dissonanza cognitiva, poiché il metodo []= sta cercando di fare molte cose diverse a seconda dell'input, e il risultato non può essere previsto a meno che tu non sappia solo come funzionano i panda. Pertanto consiglierei il []= nelle basi di codice, ma quando esploro i dati in un notebook, va bene.

Aggirare il problema

Se hai un pd.Series e vuoi che venga assegnato dall'alto verso il basso, o se stai codificando codice produttivo e non sei sicuro dell'ordine dell'indice, vale la pena salvaguardarlo per questo tipo di problema.

Si potrebbe downcast il pd.Series a un np.ndarray o una list , questo farà il trucco.

df['protected'] = pd.Series(['no', 'no', 'no', 'yes']).values

o

df['protected'] = list(pd.Series(['no', 'no', 'no', 'yes']))

Ma questo non è molto esplicito.

Qualche coder potrebbe arrivare e dire "Ehi, questo sembra ridondante, lo ottimizzerò semplicemente".

Modo esplicito

L'impostazione dell'indice del pd.Series per essere l'indice del df è esplicito.

df['protected'] = pd.Series(['no', 'no', 'no', 'yes'], index=df.index)

O più realisticamente, probabilmente hai un pd.Series già disponibile.

protected_series = pd.Series(['no', 'no', 'no', 'yes'])
protected_series.index = df.index

3     no
2     no
1     no
0    yes

Ora può essere assegnato

df['protected'] = protected_series

    size      name color protected
3    big      rose   red        no
2  small    violet  blue        no
1  small     tulip   red        no
0  small  harebell  blue       yes

Modo alternativo con df.reset_index()

Poiché la dissonanza dell'indice è il problema, se ritieni che l'indice del dataframe non debba dettare le cose, puoi semplicemente eliminare l'indice, questo dovrebbe essere più veloce, ma non è molto pulito, poiché la tua funzione ora probabilmente fa due cose.

df.reset_index(drop=True)
protected_series.reset_index(drop=True)
df['protected'] = protected_series

    size      name color protected
0    big      rose   red        no
1  small    violet  blue        no
2  small     tulip   red        no
3  small  harebell  blue       yes

Nota su df.assign

Mentre df.assign rende più esplicito ciò che stai facendo, in realtà ha tutti gli stessi problemi di cui sopra []=

df.assign(protected=pd.Series(['no', 'no', 'no', 'yes']))
    size      name color protected
3    big      rose   red       yes
2  small    violet  blue        no
1  small     tulip   red        no
0  small  harebell  blue        no

Basta guardare con df.assign che la tua colonna non è chiamata self . Causerà errori Questo rende df.assign puzzolente , dato che ci sono questi tipi di artefatti nella funzione.

df.assign(self=pd.Series(['no', 'no', 'no', 'yes'])
TypeError: assign() got multiple values for keyword argument 'self'

Potresti dire: "Beh, non userò me self allora". Ma chissà come questa funzione cambierà in futuro per supportare nuovi argomenti. Forse il nome della tua colonna sarà un argomento in un nuovo aggiornamento dei panda, causando problemi con l'aggiornamento.


Fare questo direttamente tramite NumPy sarà il più efficiente:

df1['e'] = np.random.randn(sLength)

Nota il mio suggerimento originale (molto vecchio) era quello di usare la map (che è molto più lenta):

df1['e'] = df1['a'].map(lambda x: np.random.random())

Ho ottenuto il temuto SettingWithCopyWarning e non è stato risolto utilizzando la sintassi iloc. My DataFrame è stato creato da read_sql da un'origine ODBC. Utilizzando un suggerimento di Lowtech sopra, il seguente ha funzionato per me:

df.insert(len(df.columns), 'e', pd.Series(np.random.randn(sLength),  index=df.index))

Questo ha funzionato bene per inserire la colonna alla fine. Non so se è il più efficiente, ma non mi piacciono i messaggi di avvertimento. Penso che ci sia una soluzione migliore, ma non riesco a trovarla, e penso che dipenda da alcuni aspetti dell'indice.
Nota Che questo funziona solo una volta e darà un messaggio di errore se si tenta di sovrascrivere e la colonna esistente.
Nota Come sopra e da 0.16.0 assegnare è la soluzione migliore. Vedere la documentazione http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.assign.html#pandas.DataFrame.assign Funziona bene per il tipo di flusso di dati in cui non si sovrascrivono i valori intermedi.


Lasciatemi aggiungere che, proprio come per , .loc non ha risolto SettingWithCopyWarning e ho dovuto ricorrere a df.insert() . Nel mio caso il falso positivo è stato generato dall'indicizzazione della catena "falso" dict['a']['e'] , dove 'e' è la nuova colonna, e dict['a'] è un DataFrame proveniente dal dizionario.

Si noti inoltre che se si sa cosa si sta facendo, è possibile passare l'avviso utilizzando pd.options.mode.chained_assignment = None e utilizzare una delle altre soluzioni fornite qui.


Per aggiungere una nuova colonna, 'e', ​​al frame di dati esistente

 df1.loc[:,'e'] = Series(np.random.randn(sLength))

Prima di assegnare una nuova colonna, se hai dati indicizzati, devi ordinare l'indice. Almeno nel mio caso ho dovuto:

data.set_index(['index_column'], inplace=True)
"if index is unsorted, assignment of a new column will fail"        
data.sort_index(inplace = True)
data.loc['index_value1', 'column_y'] = np.random.randn(data.loc['index_value1', 'column_x'].shape[0])

Quello che segue è quello che ho fatto ... Ma sono piuttosto nuovo per i panda e in realtà per Python in generale, quindi nessuna promessa.

df = pd.DataFrame([[1, 2], [3, 4], [5,6]], columns=list('AB'))

newCol = [3,5,7]
newName = 'C'

values = np.insert(df.values,df.shape[1],newCol,axis=1)
header = df.columns.values.tolist()
header.append(newName)

df = pd.DataFrame(values,columns=header)

Se il frame dati e l'oggetto Series hanno lo stesso indice , anche pandas.concat funziona qui:

import pandas as pd
df
#          a            b           c           d
#0  0.671399     0.101208   -0.181532    0.241273
#1  0.446172    -0.243316    0.051767    1.577318
#2  0.614758     0.075793   -0.451460   -0.012493

e = pd.Series([-0.335485, -1.166658, -0.385571])    
e
#0   -0.335485
#1   -1.166658
#2   -0.385571
#dtype: float64

# here we need to give the series object a name which converts to the new  column name 
# in the result
df = pd.concat([df, e.rename("e")], axis=1)
df

#          a            b           c           d           e
#0  0.671399     0.101208   -0.181532    0.241273   -0.335485
#1  0.446172    -0.243316    0.051767    1.577318   -1.166658
#2  0.614758     0.075793   -0.451460   -0.012493   -0.385571

Nel caso in cui non hanno lo stesso indice:

e.index = df.index
df = pd.concat([df, e.rename("e")], axis=1)

Se la colonna che stai tentando di aggiungere è una variabile serie, solo:

df["new_columns_name"]=series_variable_name #this will do it for you

Funziona bene anche se stai sostituendo una colonna esistente. Digita semplicemente new_columns_name come la colonna che vuoi sostituire. Sovrascriverà solo i dati della colonna esistente con i nuovi dati della serie.


Se si desidera impostare la nuova colonna intera su un valore di base iniziale (ad esempio None ), è possibile eseguire questa operazione: df1['e'] = None

Questo in realtà assegnerebbe il tipo "oggetto" alla cella. In seguito, sei libero di inserire tipi di dati complessi, come l'elenco, in singole celle.


Sembra che nelle ultime versioni di Pandas la strada da percorrere sia l'uso di assign :

df1 = df1.assign(e=np.random.randn(sLength))

Non produce SettingWithCopyWarning .


Una cosa da notare, però, è che se lo fai

df1['e'] = Series(np.random.randn(sLength), index=df1.index)

questo sarà effettivamente un join di sinistra su df1.index. Quindi, se vuoi avere un effetto outer join, la mia soluzione probabilmente imperfetta è creare un dataframe con valori di indice che coprano l'universo dei tuoi dati, e quindi utilizzare il codice sopra. Per esempio,

data = pd.DataFrame(index=all_possible_values)
df1['e'] = Series(np.random.randn(sLength), index=df1.index)

Utilizza gli indici originali df1 per creare la serie:

df1['e'] = Series(np.random.randn(sLength), index=df1.index)

Modifica 2015
Alcuni hanno riferito di ottenere SettingWithCopyWarning con questo codice.
Tuttavia, il codice funziona ancora perfettamente con la versione corrente dei panda 0.16.1.

>>> sLength = len(df1['a'])
>>> df1
          a         b         c         d
6 -0.269221 -0.026476  0.997517  1.294385
8  0.917438  0.847941  0.034235 -0.448948

>>> df1['e'] = p.Series(np.random.randn(sLength), index=df1.index)
>>> df1
          a         b         c         d         e
6 -0.269221 -0.026476  0.997517  1.294385  1.757167
8  0.917438  0.847941  0.034235 -0.448948  2.228131

>>> p.version.short_version
'0.16.1'

Il SettingWithCopyWarning scopo di informare di un eventuale assegnazione non valida su una copia del Dataframe. Non dice necessariamente che hai sbagliato (può far scattare falsi positivi) ma da 0.13.0 ti fa sapere che esistono metodi più adeguati per lo stesso scopo. Quindi, se ricevi l'avviso, segui semplicemente i suoi consigli: prova a utilizzare .loc [row_index, col_indexer] = valore invece

>>> df1.loc[:,'f'] = p.Series(np.random.randn(sLength), index=df1.index)
>>> df1
          a         b         c         d         e         f
6 -0.269221 -0.026476  0.997517  1.294385  1.757167 -0.050927
8  0.917438  0.847941  0.034235 -0.448948  2.228131  0.006109
>>> 

In effetti, questo è attualmente il metodo più efficiente come descritto in documenti panda

Modifica 2017

Come indicato nei commenti e da @Alexander, attualmente il metodo migliore per aggiungere i valori di una serie come una nuova colonna di un DataFrame potrebbe essere l'utilizzo di assign :

df1 = df1.assign(e=p.Series(np.random.randn(sLength)).values)




chained-assignment