web-dev-qa-db-fra.com

Ajout d'une nouvelle colonne à un DataFrame existant dans Python pandas

J'ai le DataFrame indexé suivant avec des colonnes nommées et des lignes non continues:

          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

J'aimerais ajouter une nouvelle colonne, 'e', au cadre de données existant et je ne souhaite rien modifier dans le cadre de données (c'est-à-dire que la nouvelle colonne a toujours la même longueur que le DataFrame).

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

J'ai essayé différentes versions de join, append, merge, mais je n'ai pas obtenu le résultat que je voulais, mais des erreurs tout au plus. Comment puis-je ajouter la colonne e à l'exemple ci-dessus?

844
tomasz74

Utilisez les index df1 d'origine pour créer la série:

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

Éditer 2015
Certains ont rapporté avoir obtenu la SettingWithCopyWarning avec ce code.
Cependant, le code fonctionne toujours parfaitement avec la version actuelle de pandas version 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'] = pd.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'

La SettingWithCopyWarning a pour but d'informer d'une assignation éventuellement non valide sur une copie du Dataframe. Cela ne signifie pas nécessairement que vous vous êtes trompé (cela peut déclencher des faux positifs), mais à partir de 0.13.0, cela vous indique qu'il existe des méthodes plus adéquates pour le même objectif. Ensuite, si vous recevez cet avertissement, suivez simplement ses conseils: Essayez d’utiliser .loc [index_ligne, index_col] = valeur

>>> df1.loc[:,'f'] = pd.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
>>> 

En fait, c’est actuellement la méthode la plus efficace en tant que décrite dans pandas docs


Éditer 2017

Comme indiqué dans les commentaires et par @Alexander, la meilleure méthode pour ajouter les valeurs d'une série en tant que nouvelle colonne d'un DataFrame pourrait être d'utiliser assign:

df1 = df1.assign(e=pd.Series(np.random.randn(sLength)).values)
932
joaquin

C'est le moyen simple d'ajouter une nouvelle colonne: df['e'] = e

196
Kathirmani Sukumar

Je voudrais ajouter une nouvelle colonne, 'e', ​​au bloc de données existant et ne rien changer dans le bloc de données. (La série a toujours la même longueur qu’une image de données.)

Je suppose que les valeurs d'index dans e correspondent à celles de df1.

La méthode la plus simple pour créer une nouvelle colonne nommée e et lui attribuer les valeurs de votre série e:

df['e'] = e.values

assigner (Pandas 0.16.0 +)

À partir de Pandas 0.16.0, vous pouvez également utiliser assign , qui attribue de nouvelles colonnes à un DataFrame et renvoie un nouvel objet (une copie) avec toutes les colonnes originales, en plus de les nouvelles.

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

Selon cet exemple (qui inclut également le code source de la fonction assign), vous pouvez également inclure plusieurs colonnes:

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

Dans le contexte de votre exemple:

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 description de cette nouvelle fonctionnalité lors de son introduction a été trouvée ici .

136
Alexander

Faire cela directement via NumPy sera le plus efficace:

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

Notez ma suggestion (très ancienne) originale d'utiliser map (qui est beaucoup plus lent):

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

Il semble que dans les récentes versions de Pandas, la solution consiste à utiliser df.assign :

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

Il ne produit pas SettingWithCopyWarning.

45
Mikhail Korobov

Affectation de colonne super simple

Un pandas dataframe est implémenté comme un dict ordonné de colonnes.

Cela signifie que le __getitem__[] ne peut pas seulement être utilisé pour obtenir une certaine colonne, mais __setitem__[] = peut être utilisé pour affecter une nouvelle colonne.

Par exemple, une colonne peut être ajoutée à cette image de données simplement à l'aide de l'accesseur [].

    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

Notez que cela fonctionne même si l'index de la trame de données est désactivé.

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

[] = est le chemin à parcourir, mais attention!

Cependant, si vous avez un pd.Series et essayez de l'assigner à une trame de données où les index sont désactivés, vous rencontrerez des problèmes. Voir exemple:

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

En effet, un pd.Series par défaut a un index énuméré de 0 à n. Et la pandas [] = méthode tente d'être "intelligente"

Qu'est-ce qui se passe réellement.

Lorsque vous utilisez la méthode [] =, pandas effectue discrètement une jointure externe ou une fusion externe à l'aide de l'index de la trame de données de gauche et de la série de droite. df['column'] = series

Note latérale

Cela provoque rapidement une dissonance cognitive, car la méthode []= essaie de faire beaucoup de choses différentes en fonction de l’entrée, et le résultat ne peut pas être prédit à moins que vous ne sachiez que comment pandas fonctionne. Je déconseillerais donc le []= dans les bases de code, mais lors de l'exploration de données dans un cahier, tout va bien.

Contourner le problème

Si vous avez un pd.Series et que vous voulez l’affecter de haut en bas, ou si vous codez du code productif et que vous n'êtes pas sûr de l’ordre d’index, il vaut la peine de le protéger pour ce type de problème.

Vous pouvez downcast le pd.Series en un np.ndarray ou un list, cela fera l'affaire.

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

ou

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

Mais ce n'est pas très explicite.

Certains codeurs peuvent venir et dire "Hé, ça a l'air redondant, je vais juste l'optimiser".

Manière explicite

Définir l'index du pd.Series pour qu'il soit l'index du df est explicite.

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

De manière plus réaliste, vous avez probablement déjà un pd.Series disponible.

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

3     no
2     no
1     no
0    yes

Peut maintenant être assigné

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

Manière alternative avec df.reset_index()

Puisque la dissonance de l’index est le problème, si vous estimez que l’index de la structure de données ne devrait pas dicter , vous pouvez simplement supprimer l’index. plus rapide, mais ce n’est pas très propre, puisque votre fonction effectue maintenant probablement deux choses.

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

Note sur df.assign

Bien que df.assign rende plus explicite ce que vous faites, il présente en fait tous les mêmes problèmes que ceux décrits ci-dessus []=

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

Faites juste attention avec df.assign que votre colonne ne s'appelle pas self. Cela causera des erreurs. Cela rend df.assignmalodorant, car il y a ce genre d'artefacts dans la fonction.

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

Vous pouvez dire: "Eh bien, je ne vais tout simplement pas utiliser self alors". Mais qui sait comment cette fonction changera à l'avenir pour supporter de nouveaux arguments. Peut-être que votre nom de colonne sera un argument dans une nouvelle mise à jour de pandas, ce qui posera des problèmes de mise à niveau.

35
firelynx

Si vous souhaitez définir la nouvelle colonne entière sur une valeur de base initiale (par exemple, None), procédez comme suit: df1['e'] = None

En réalité, cela assignerait le type "objet" à la cellule. Vous êtes donc libre de placer ultérieurement des types de données complexes, tels que list, dans des cellules individuelles.

22
digdug

Moyens les plus simples: -

data['new_col'] = list_of_values

data.loc[ : , 'new_col'] = list_of_values
20
Abhishek

J'ai eu le redouté SettingWithCopyWarning, et cela n'a pas été corrigé en utilisant la syntaxe iloc. Mon DataFrame a été créé par read_sql à partir d'une source ODBC. En utilisant une suggestion de lowtech ci-dessus, les éléments suivants ont fonctionné pour moi:

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

Cela a bien fonctionné pour insérer la colonne à la fin. Je ne sais pas si c'est le plus efficace, mais je n'aime pas les messages d'avertissement. Je pense qu'il existe une meilleure solution, mais je ne la trouve pas et cela dépend d'un aspect de l'indice.
Remarque . Cela ne fonctionne qu'une fois et donnera un message d'erreur si vous essayez d'écraser une colonne existante.
Remarque Comme ci-dessus et à partir de 0.16.0, attribuer est la meilleure solution. Voir la documentation http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.assign.html#pandas.DataFrame.assign Fonctionne bien pour le type de flux de données où vous ne le faites pas. t écrasez vos valeurs intermédiaires.

18
hum3
  1. Commencez par créer un list_of_e de python contenant des données pertinentes.
  2. Utilisez ceci: df['e'] = list_of_e
13
Sumit Pokhrel

Si la colonne que vous essayez d'ajouter est une variable de série, alors simplement:

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

Cela fonctionne bien, même si vous remplacez une colonne existante. Tapez simplement new_columns_name identique à la colonne que vous souhaitez remplacer. Il remplacera simplement les données de colonne existantes par les nouvelles données de série.

11
Akshay Singhvi

À toute épreuve:

df.loc[:, 'NewCol'] = 'New_Val'

Exemple:

df = pd.DataFrame(data=np.random.randn(20, 4), columns=['A', 'B', 'C', 'D'])

df

           A         B         C         D
0  -0.761269  0.477348  1.170614  0.752714
1   1.217250 -0.930860 -0.769324 -0.408642
2  -0.619679 -1.227659 -0.259135  1.700294
3  -0.147354  0.778707  0.479145  2.284143
4  -0.529529  0.000571  0.913779  1.395894
5   2.592400  0.637253  1.441096 -0.631468
6   0.757178  0.240012 -0.553820  1.177202
7  -0.986128 -1.313843  0.788589 -0.707836
8   0.606985 -2.232903 -1.358107 -2.855494
9  -0.692013  0.671866  1.179466 -1.180351
10 -1.093707 -0.530600  0.182926 -1.296494
11 -0.143273 -0.503199 -1.328728  0.610552
12 -0.923110 -1.365890 -1.366202 -1.185999
13 -2.026832  0.273593 -0.440426 -0.627423
14 -0.054503 -0.788866 -0.228088 -0.404783
15  0.955298 -1.430019  1.434071 -0.088215
16 -0.227946  0.047462  0.373573 -0.111675
17  1.627912  0.043611  1.743403 -0.012714
18  0.693458  0.144327  0.329500 -0.655045
19  0.104425  0.037412  0.450598 -0.923387


df.drop([3, 5, 8, 10, 18], inplace=True)

df

           A         B         C         D
0  -0.761269  0.477348  1.170614  0.752714
1   1.217250 -0.930860 -0.769324 -0.408642
2  -0.619679 -1.227659 -0.259135  1.700294
4  -0.529529  0.000571  0.913779  1.395894
6   0.757178  0.240012 -0.553820  1.177202
7  -0.986128 -1.313843  0.788589 -0.707836
9  -0.692013  0.671866  1.179466 -1.180351
11 -0.143273 -0.503199 -1.328728  0.610552
12 -0.923110 -1.365890 -1.366202 -1.185999
13 -2.026832  0.273593 -0.440426 -0.627423
14 -0.054503 -0.788866 -0.228088 -0.404783
15  0.955298 -1.430019  1.434071 -0.088215
16 -0.227946  0.047462  0.373573 -0.111675
17  1.627912  0.043611  1.743403 -0.012714
19  0.104425  0.037412  0.450598 -0.923387

df.loc[:, 'NewCol'] = 0

df
           A         B         C         D  NewCol
0  -0.761269  0.477348  1.170614  0.752714       0
1   1.217250 -0.930860 -0.769324 -0.408642       0
2  -0.619679 -1.227659 -0.259135  1.700294       0
4  -0.529529  0.000571  0.913779  1.395894       0
6   0.757178  0.240012 -0.553820  1.177202       0
7  -0.986128 -1.313843  0.788589 -0.707836       0
9  -0.692013  0.671866  1.179466 -1.180351       0
11 -0.143273 -0.503199 -1.328728  0.610552       0
12 -0.923110 -1.365890 -1.366202 -1.185999       0
13 -2.026832  0.273593 -0.440426 -0.627423       0
14 -0.054503 -0.788866 -0.228088 -0.404783       0
15  0.955298 -1.430019  1.434071 -0.088215       0
16 -0.227946  0.047462  0.373573 -0.111675       0
17  1.627912  0.043611  1.743403 -0.012714       0
19  0.104425  0.037412  0.450598 -0.923387       0
10
K88

e = [ -0.335485, -1.166658, -0.385571]

moyen simple et facile

df['e'] = e

9
Nursnaaz

Si le bloc de données et l'objet Series ont le même index , pandas.concat fonctionne également ici:

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

Au cas où ils n'auraient pas le même index:

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

Permettez-moi simplement d’ajouter que, tout comme pour hum , .loc n’a pas résolu le problème SettingWithCopyWarning et j’ai dû recourir à df.insert(). Dans mon cas, les faux positifs ont été générés par la chaîne "fictive" d'indexation dict['a']['e'], où 'e' est la nouvelle colonne et dict['a'] est un DataFrame provenant du dictionnaire.

Notez également que si vous savez ce que vous faites, vous pouvez désactiver l'avertissement à l'aide de pd.options.mode.chained_assignment = None et utiliser l'une des solutions présentées ici.

6
kkumer

Avant d’affecter une nouvelle colonne, si vous avez des données indexées, vous devez trier l’index. Au moins dans mon cas je devais:

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])
6
Dima Lituiev

Une chose à noter, cependant, est que si vous le faites

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

ce sera effectivement un à gauche rejoindre sur le df1.index. Donc, si vous voulez avoir un effet de jointure externe, ma solution probablement imparfaite est de créer un cadre de données avec des valeurs d'index couvrant l'univers de vos données, puis d'utiliser le code ci-dessus. Par exemple,

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

Je cherchais un moyen général d’ajouter une colonne de numpy.nans à un cadre de données sans obtenir le dumb SettingWithCopyWarning.

De ce qui suit:

  • les réponses ici
  • cette question à propos de passer une variable en tant qu'argument de mot clé
  • cette méthode pour générer un tableau numpy de NaN en ligne

Je suis venu avec ceci:

col = 'column_name'
df = df.assign(**{col:numpy.full(len(df), numpy.nan)})
5
ryanjdillon

Par souci d’exhaustivité - encore une autre solution utilisant la méthode DataFrame.eval () :

Les données:

In [44]: e
Out[44]:
0    1.225506
1   -1.033944
2   -0.498953
3   -0.373332
4    0.615030
5   -0.622436
dtype: float64

In [45]: df1
Out[45]:
          a         b         c         d
0 -0.634222 -0.103264  0.745069  0.801288
4  0.782387 -0.090279  0.757662 -0.602408
5 -0.117456  2.124496  1.057301  0.765466
7  0.767532  0.104304 -0.586850  1.051297
8 -0.103272  0.958334  1.163092  1.182315
9 -0.616254  0.296678 -0.112027  0.679112

Solution:

In [46]: df1.eval("e = @e.values", inplace=True)

In [47]: df1
Out[47]:
          a         b         c         d         e
0 -0.634222 -0.103264  0.745069  0.801288  1.225506
4  0.782387 -0.090279  0.757662 -0.602408 -1.033944
5 -0.117456  2.124496  1.057301  0.765466 -0.498953
7  0.767532  0.104304 -0.586850  1.051297 -0.373332
8 -0.103272  0.958334  1.163092  1.182315  0.615030
9 -0.616254  0.296678 -0.112027  0.679112 -0.622436
4
MaxU

Pour ajouter une nouvelle colonne, 'e', ​​au bloc de données existant

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

Ce qui suit est ce que j’ai fait ... Mais je suis assez nouveau pour pandas et vraiment Python en général, donc aucune promesse.

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)
3
Devin Charles

pour insérer une nouvelle colonne à un emplacement donné (0 <= loc <= nombre de colonnes) dans un cadre de données, utilisez simplement Dataframe.insert:

DataFrame.insert(loc, column, value)

Par conséquent, si vous souhaitez ajouter la colonne e à la fin d'un bloc de données appelé df , vous pouvez utiliser:

e = [-0.335485, -1.166658, -0.385571]    
DataFrame.insert(loc=len(df.columns), column='e', value=e)

valeur peut être une série, un entier (auquel cas toutes les cellules sont remplies avec cette seule valeur). ), ou une structure semblable à un tableau

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.insert.html

3
Nooyi

Si vous obtenez le SettingWithCopyWarning, une solution simple consiste à copier le DataFrame auquel vous essayez d'ajouter une colonne.

df = df.copy()
df['col_name'] = values
3
fredcallaway