Définir la valeur d'une cellule particulière dans pandas DataFrame à l'aide d'un index


479

J'ai créé un Pandas DataFrame

df = DataFrame(index=['A','B','C'], columns=['x','y'])

et j'ai eu ça

    xy
A NaN NaN
B NaN NaN
C NaN NaN


Ensuite, je veux attribuer une valeur à une cellule particulière, par exemple pour la ligne «C» et la colonne «x». Je m'attendais à obtenir un tel résultat:

    xy
A NaN NaN
B NaN NaN
C 10 NaN

avec ce code:

df.xs('C')['x'] = 10

mais le contenu de dfn'a pas changé. C'est encore seulement NaNs dans DataFrame.

Aucune suggestion?


29
N'utilisez pas l'indexation chaînée ( df['x']['C']), utilisez df.ix['x','C'].
Yariv

3
L'ordre d'accès à l'index doit être dataframe[column (series)] [row (Series index)]:, tandis que de nombreuses personnes (y compris moi-même) sont plus habituées à l' dataframe[row][column]ordre. En tant que programmeur Matlab et R, ce dernier me semble plus intuitif, mais ce n'est apparemment pas la façon dont Pandas fonctionne ..
Zhubarb

1
j'ai essayé, mais j'ai fini par ajouter un autre nom de ligne x et un autre nom de colonne C. vous devez d'abord faire la ligne puis la colonne. donc df.ix ['C', 'x'] = 10
Matthew

5
Au commentaire de @ Yariv. Avertissement: à partir de la version 0.20.0, l'indexeur .ix est obsolète, au profit des indexeurs .iloc et .loc plus stricts. pandas.pydata.org/pandas-docs/stable/generated/… . df.at a l'air de rester.
jeffhale

Réponses:


594

La réponse de RukTech , df.set_value('C', 'x', 10)est loin plus rapide que les options que je l' ai suggéré ci - dessous. Cependant, il est prévu qu'il soit déprécié .

À l'avenir, la méthode recommandée est.iat/.at .


Pourquoi df.xs('C')['x']=10ne fonctionne pas:

df.xs('C')par défaut, renvoie une nouvelle trame de données avec une copie des données, donc

df.xs('C')['x']=10

modifie uniquement cette nouvelle trame de données.

df['x']renvoie une vue de la dftrame de données, donc

df['x']['C'] = 10

se modifie df.

Avertissement : Il est parfois difficile de prévoir si une opération renvoie une copie ou une vue. Pour cette raison, les documents recommandent d'éviter les affectations avec "l'indexation chaînée" .


Donc, l'alternative recommandée est

df.at['C', 'x'] = 10

qui ne modifie df.


In [18]: %timeit df.set_value('C', 'x', 10)
100000 loops, best of 3: 2.9 µs per loop

In [20]: %timeit df['x']['C'] = 10
100000 loops, best of 3: 6.31 µs per loop

In [81]: %timeit df.at['C', 'x'] = 10
100000 loops, best of 3: 9.2 µs per loop

Il n'y a rien de tel que df.xdans l' API . Que voulais-tu dire?
smci

3
@smci: 'x'est le nom d'une colonne dans df. df.xrenvoie un Seriesavec les valeurs dans la colonne x. Je vais le changer df['x']car cette notation fonctionnera avec n'importe quel nom de colonne (contrairement à la notation par points) et je pense que c'est plus clair.
unutbu

1
Je le savais, je pensais que vous disiez qu'il y df.xavait une nouvelle méthode inconnue aux côtésdf.xs, df.ix
smci

df.xs(..., copy=True)renvoie une copie, et c'est le comportement par défaut. df.xs(..., copy=False)renvoie l'original.
smci

7
Selon les responsables, ce n'est pas la méthode recommandée pour définir une valeur. Voir stackoverflow.com/a/21287235/1579844 et ma réponse.
Yariv

225

Mise à jour: la .set_valueméthode va être déconseillée . .iat/.atsont de bons remplaçants, malheureusement les pandas fournissent peu de documentation


La façon la plus rapide de le faire est d'utiliser set_value . Cette méthode est ~ 100 fois plus rapide que la .ixméthode. Par exemple:

df.set_value('C', 'x', 10)


5
C'est encore mieux que df['x']['C'] = 10 .
ALH

6
1000 boucles, meilleur de 3: 195 µs par boucle "df ['x'] ['C'] = 10" 1000 boucles, meilleur de 3: 310 µs par boucle "df.ix ['C', 'x'] = 10 "1000 boucles, meilleur de 3: 189 µs par boucle" df.xs ('C', copie = faux) ['x'] = 10 "1000 boucles, meilleur de 3: 7,22 µs par boucle" df.set_value ('C', 'x', 10) "
propjk007

1
cela fonctionne-t-il également pour ajouter une nouvelle ligne / colonne à la trame de données?
st.ph.n

Oui (pour les pandas 0.16.2)
RukTech

Est-il possible de l'utiliser pour définir une valeur à a df=df.append(df.sum(numeric_only=True),ignore_index=True)?
ctrl-alt-delete

95

Vous pouvez également utiliser une recherche conditionnelle en utilisant .loccomme vu ici:

df.loc[df[<some_column_name>] == <condition>, [<another_column_name>]] = <value_to_add>

<some_column_nameest la colonne que vous souhaitez vérifier la <condition>variable et <another_column_name>est la colonne que vous souhaitez ajouter (peut être une nouvelle colonne ou une qui existe déjà). <value_to_add>est la valeur que vous souhaitez ajouter à cette colonne / ligne.

Cet exemple ne fonctionne pas précisément avec la question posée, mais il peut être utile pour quelqu'un qui souhaite ajouter une valeur spécifique en fonction d'une condition.


8
la deuxième colonne doit être entre parenthèses, sinon toutes les colonnes seront écrasées par la valeur. Comme ça:df.loc[df['age']==3, ['age-group']] = 'toddler'
Piizei

Je ne peux pas faire fonctionner cela lorsque <some_column_name> est mon index (par exemple, unixtime index) et j'essaie d'ajouter un horodatage qui ne se termine pas encore (c'est-à-dire une nouvelle lecture d'horodatage). Des pensées?
yeliabsalohcin

Est-il possible de modifier une valeur en fonction des valeurs d'index et de cellule?
BND

@BND Je ne suis pas sûr, mais vous pourriez contourner cet écueil apparent mais simplement dupliquer la colonne d'index avec une autre colonne avec la même valeur? La réponse courte est que je ne sais pas.
Blairg23

@yeliabsalohcin voir la réponse ci-dessus.
Blairg23

40

La méthode recommandée (selon les responsables) pour définir une valeur est la suivante:

df.ix['x','C']=10

L'utilisation de l'indexation chaînée ( df['x']['C']) peut entraîner des problèmes.

Voir:



fonctionne parfaitement! même si ça va être déprécié un jour!
Pavlos Ponos

35

Essayez d'utiliser df.loc[row_index,col_indexer] = value


6
Bienvenue dans Stack Overflow! Veuillez envisager de modifier votre message pour ajouter plus d'explications sur ce que fait votre code et pourquoi il résoudra le problème. Une réponse qui contient principalement du code (même si cela fonctionne) n'aidera généralement pas l'OP à comprendre leur problème. Il est également recommandé de ne pas publier de réponse si ce n'est qu'une supposition. Une bonne réponse aura une raison plausible pour laquelle elle pourrait résoudre le problème du PO.
SuperBiasMan

22

C'est la seule chose qui a fonctionné pour moi!

df.loc['C', 'x'] = 10

En savoir plus .loc ici .


a .locremplacé .iat/.at?
Gabriel Fair

1
atSimilaire à loc, dans la mesure où les deux fournissent des recherches basées sur des étiquettes. À utiliser atsi vous avez uniquement besoin d'obtenir ou de définir une valeur unique dans un DataFrame ou une série. De padas doc
Rutrus

Bien, cela a fonctionné pour moi lorsque mes éléments d'index étaient numériques.
Christopher John

Cela ne fonctionne pas pour un mélange d'indices numériques et de chaînes.
Seanny123

12

.iat/.atest la bonne solution. Supposons que vous ayez ce simple data_frame:

   A   B   C
0  1   8   4 
1  3   9   6
2  22 33  52

si nous voulons modifier la valeur de la cellule, [0,"A"]u peut utiliser l'une de ces solutions:

  1. df.iat[0,0] = 2
  2. df.at[0,'A'] = 2

Et voici un exemple complet comment utiliser iatpour obtenir et définir une valeur de cellule:

def prepossessing(df):
  for index in range(0,len(df)): 
      df.iat[index,0] = df.iat[index,0] * 2
  return df

y_train avant:

    0
0   54
1   15
2   15
3   8
4   31
5   63
6   11

y_train après avoir appelé la fonction de pré-possession que iatchanger pour multiplier la valeur de chaque cellule par 2:

     0
0   108
1   30
2   30
3   16
4   62
5   126
6   22

8

Pour définir des valeurs, utilisez:

df.at[0, 'clm1'] = 0
  • La méthode recommandée la plus rapide pour définir des variables.
  • set_value, ixsont obsolètes.
  • Aucun avertissement, contrairement à ilocetloc

1
Je suis arrivé exactement à la même conclusion .
prosti

6

vous pouvez utiliser .iloc.

df.iloc[[2], [0]] = 10

Cette méthode ne semble pas supporter plusieurs valeurs, par exemple df.iloc[[2:8], [0]] = [2,3,4,5,6,7]ce que la méthode df.loc()fait nativement.
strpeter

1
fonctionne parfaitement, sans avertissement de dépréciation!
Pavlos Ponos,

6

Dans mon exemple, je viens de le changer dans la cellule sélectionnée

    for index, row in result.iterrows():
        if np.isnan(row['weight']):
            result.at[index, 'weight'] = 0.0

'result' est un dataField avec la colonne 'weight'


4

set_value() est obsolète.

A partir de la version 0.23.4, Pandas " annonce le futur " ...

>>> df
                   Cars  Prices (U$)
0               Audi TT        120.0
1 Lamborghini Aventador        245.0
2      Chevrolet Malibu        190.0
>>> df.set_value(2, 'Prices (U$)', 240.0)
__main__:1: FutureWarning: set_value is deprecated and will be removed in a future release.
Please use .at[] or .iat[] accessors instead

                   Cars  Prices (U$)
0               Audi TT        120.0
1 Lamborghini Aventador        245.0
2      Chevrolet Malibu        240.0

Compte tenu de ces conseils, voici une démonstration de la façon de les utiliser:

  • par positions entières de ligne / colonne

>>> df.iat[1, 1] = 260.0
>>> df
                   Cars  Prices (U$)
0               Audi TT        120.0
1 Lamborghini Aventador        260.0
2      Chevrolet Malibu        240.0
  • par étiquettes de ligne / colonne

>>> df.at[2, "Cars"] = "Chevrolet Corvette"
>>> df
                  Cars  Prices (U$)
0               Audi TT        120.0
1 Lamborghini Aventador        260.0
2    Chevrolet Corvette        240.0

Références:


3

Voici un résumé des solutions valides fournies par tous les utilisateurs, pour les trames de données indexées par entier et chaîne.

df.iloc, df.loc et df.at fonctionnent pour les deux types de trames de données, df.iloc ne fonctionne qu'avec des indices entiers de ligne / colonne, df.loc et df.at prennent en charge la définition de valeurs à l'aide de noms de colonne et / ou d'indices entiers .

Lorsque l'index spécifié n'existe pas, df.loc et df.at ajoutent les lignes / colonnes nouvellement insérées au bloc de données existant, mais df.iloc déclenche "IndexError: les indexeurs de position sont hors limites". Un exemple de travail testé en Python 2.7 et 3.7 est le suivant:

import numpy as np, pandas as pd

df1 = pd.DataFrame(index=np.arange(3), columns=['x','y','z'])
df1['x'] = ['A','B','C']
df1.at[2,'y'] = 400

# rows/columns specified does not exist, appends new rows/columns to existing data frame
df1.at['D','w'] = 9000
df1.loc['E','q'] = 499

# using df[<some_column_name>] == <condition> to retrieve target rows
df1.at[df1['x']=='B', 'y'] = 10000
df1.loc[df1['x']=='B', ['z','w']] = 10000

# using a list of index to setup values
df1.iloc[[1,2,4], 2] = 9999
df1.loc[[0,'D','E'],'w'] = 7500
df1.at[[0,2,"D"],'x'] = 10
df1.at[:, ['y', 'w']] = 8000

df1
>>> df1
     x     y     z     w      q
0   10  8000   NaN  8000    NaN
1    B  8000  9999  8000    NaN
2   10  8000  9999  8000    NaN
D   10  8000   NaN  8000    NaN
E  NaN  8000  9999  8000  499.0

3

J'ai testé et la sortie est un df.set_valuepeu plus rapide, mais la méthode officielle df.atressemble à la méthode non obsolète la plus rapide pour le faire.

import numpy as np
import pandas as pd

df = pd.DataFrame(np.random.rand(100, 100))

%timeit df.iat[50,50]=50 # ✓
%timeit df.at[50,50]=50 #  ✔
%timeit df.set_value(50,50,50) # will deprecate
%timeit df.iloc[50,50]=50
%timeit df.loc[50,50]=50

7.06 µs ± 118 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
5.52 µs ± 64.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
3.68 µs ± 80.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
98.7 µs ± 1.07 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
109 µs ± 1.42 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Notez que cela définit la valeur d'une seule cellule. Pour les vecteurs locet ilocdevraient être de meilleures options car ils sont vectorisés.


3

Une façon d'utiliser l'index avec condition est tout d'abord d'obtenir l'index de toutes les lignes qui satisfont votre condition, puis d'utiliser simplement ces index de ligne de plusieurs façons

conditional_index = df.loc[ df['col name'] <condition> ].index

La condition d'exemple est comme

==5, >10 , =="Any string", >= DateTime

Ensuite, vous pouvez utiliser ces index de ligne de différentes manières, comme

  1. Remplacer la valeur d'une colonne pour conditional_index
df.loc[conditional_index , [col name]]= <new value>
  1. Remplacer la valeur de plusieurs colonnes pour conditional_index
df.loc[conditional_index, [col1,col2]]= <new value>
  1. L'un des avantages de l'enregistrement de l'index_conditionnel est que vous pouvez attribuer la valeur d'une colonne à une autre colonne avec le même index de ligne
df.loc[conditional_index, [col1,col2]]= df.loc[conditional_index,'col name']

Tout cela est possible car .index renvoie un tableau d'index que .loc peut utiliser avec l'adressage direct, ce qui évite les traversées encore et encore.


qu'en est-il de la modification des lignes?
FabioSpaghetti

utilisez simplement, df.loc [conditional_index,] = <nouvelle valeur> Il remplacera la nouvelle valeur dans toutes les colonnes de lignes qui remplissent la condition
Atta Jutt

2

df.loc['c','x']=10 Cela changera la valeur de la c ème ligne et de la x ème colonne.


1

En plus des réponses ci-dessus, voici un benchmark comparant différentes façons d'ajouter des lignes de données à une trame de données déjà existante. Il montre que l'utilisation de at ou set-value est le moyen le plus efficace pour les grandes trames de données (au moins pour ces conditions de test).

  • Créez un nouveau cadre de données pour chaque ligne et ...
    • ... l'ajouter (13.0 s)
    • ... concaténer (13,1 s)
  • Stockez d'abord toutes les nouvelles lignes dans un autre conteneur, convertissez-les une fois en nouvelle trame de données et ajoutez-les ...
    • conteneur = listes de listes (2.0 s)
    • conteneur = dictionnaire de listes (1,9 s)
  • Préallouez la trame de données entière, parcourez les nouvelles lignes et toutes les colonnes et remplissez à l'aide de
    • ... à (0,6 s)
    • ... set_value (0,4 s)

Pour le test, une trame de données existante comprenant 100 000 lignes et 1 000 colonnes et des valeurs numpy aléatoires a été utilisée. À cette trame de données, 100 nouvelles lignes ont été ajoutées.

Code voir ci-dessous:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Nov 21 16:38:46 2018

@author: gebbissimo
"""

import pandas as pd
import numpy as np
import time

NUM_ROWS = 100000
NUM_COLS = 1000
data = np.random.rand(NUM_ROWS,NUM_COLS)
df = pd.DataFrame(data)

NUM_ROWS_NEW = 100
data_tot = np.random.rand(NUM_ROWS + NUM_ROWS_NEW,NUM_COLS)
df_tot = pd.DataFrame(data_tot)

DATA_NEW = np.random.rand(1,NUM_COLS)


#%% FUNCTIONS

# create and append
def create_and_append(df):
    for i in range(NUM_ROWS_NEW):
        df_new = pd.DataFrame(DATA_NEW)
        df = df.append(df_new)
    return df

# create and concatenate
def create_and_concat(df):
    for i in range(NUM_ROWS_NEW):
        df_new = pd.DataFrame(DATA_NEW)
        df = pd.concat((df, df_new))
    return df


# store as dict and 
def store_as_list(df):
    lst = [[] for i in range(NUM_ROWS_NEW)]
    for i in range(NUM_ROWS_NEW):
        for j in range(NUM_COLS):
            lst[i].append(DATA_NEW[0,j])
    df_new = pd.DataFrame(lst)
    df_tot = df.append(df_new)
    return df_tot

# store as dict and 
def store_as_dict(df):
    dct = {}
    for j in range(NUM_COLS):
        dct[j] = []
        for i in range(NUM_ROWS_NEW):
            dct[j].append(DATA_NEW[0,j])
    df_new = pd.DataFrame(dct)
    df_tot = df.append(df_new)
    return df_tot




# preallocate and fill using .at
def fill_using_at(df):
    for i in range(NUM_ROWS_NEW):
        for j in range(NUM_COLS):
            #print("i,j={},{}".format(i,j))
            df.at[NUM_ROWS+i,j] = DATA_NEW[0,j]
    return df


# preallocate and fill using .at
def fill_using_set(df):
    for i in range(NUM_ROWS_NEW):
        for j in range(NUM_COLS):
            #print("i,j={},{}".format(i,j))
            df.set_value(NUM_ROWS+i,j,DATA_NEW[0,j])
    return df


#%% TESTS
t0 = time.time()    
create_and_append(df)
t1 = time.time()
print('Needed {} seconds'.format(t1-t0))

t0 = time.time()    
create_and_concat(df)
t1 = time.time()
print('Needed {} seconds'.format(t1-t0))

t0 = time.time()    
store_as_list(df)
t1 = time.time()
print('Needed {} seconds'.format(t1-t0))

t0 = time.time()    
store_as_dict(df)
t1 = time.time()
print('Needed {} seconds'.format(t1-t0))

t0 = time.time()    
fill_using_at(df_tot)
t1 = time.time()
print('Needed {} seconds'.format(t1-t0))

t0 = time.time()    
fill_using_set(df_tot)
t1 = time.time()
print('Needed {} seconds'.format(t1-t0))

0

Si vous souhaitez modifier les valeurs non pas pour la ligne entière, mais uniquement pour certaines colonnes:

x = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})
x.iloc[1] = dict(A=10, B=-10)

0

Depuis la version 0.21.1, vous pouvez également utiliser la .atméthode. Il y a quelques différences par rapport à ce .locqui est mentionné ici - pandas .at contre .loc , mais c'est plus rapide sur le remplacement à valeur unique


0

Soo, votre question pour convertir NaN à ['x', C] en valeur 10

la réponse est..

df['x'].loc['C':]=10
df

le code alternatif est

df.loc['C':'x']=10
df

-4

Moi aussi, je cherchais ce sujet et j'ai mis au point un moyen d'itérer dans un DataFrame et de le mettre à jour avec les valeurs de recherche d'un deuxième DataFrame. Voici mon code.

src_df = pd.read_sql_query(src_sql,src_connection)
for index1, row1 in src_df.iterrows():
    for index, row in vertical_df.iterrows():
        src_df.set_value(index=index1,col=u'etl_load_key',value=etl_load_key)
        if (row1[u'src_id'] == row['SRC_ID']) is True:
            src_df.set_value(index=index1,col=u'vertical',value=row['VERTICAL'])
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.