web-dev-qa-db-fra.com

Changer une valeur basée sur une autre valeur dans pandas

J'essaie de reprogrammer mon code Stata en Python pour améliorer la vitesse, et j'ai été dirigé dans la direction de PANDAS. Cependant, j'ai du mal à comprendre comment traiter les données.

Disons que je veux parcourir toutes les valeurs dans l'en-tête de colonne 'ID'. Si cet ID correspond à un numéro spécifique, je souhaite modifier les deux valeurs correspondantes FirstName et LastName.

Dans Stata, cela ressemble à ceci:

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

Cela remplace donc toutes les valeurs de FirstName qui correspondent aux valeurs de ID == 103 à Matt.

Dans PANDAS, j'essaie quelque chose comme ça

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

Je ne sais pas où aller d'ici. Des idées?

74
Parseltongue

Une option consiste à utiliser les fonctions de tranchage et d'indexation de Python pour évaluer logiquement les emplacements où votre condition est conservée et y écraser les données.

En supposant que vous puissiez charger vos données directement dans pandas avec pandas.read_csv alors le code suivant pourrait vous être utile.

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

Comme mentionné dans les commentaires, vous pouvez également affecter les deux colonnes en une seule fois:

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

Notez que vous aurez besoin de pandas version 0.11 ou plus récente pour utiliser loc pour les opérations d’affectation de remplacement.


Une autre façon de le faire est d'utiliser ce que l'on appelle une affectation chaînée. Le comportement de celui-ci est moins stable et il n'est donc pas considéré comme la meilleure solution (c'est explicitement déconseillé dans la documentation), mais il est utile de connaître:

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

Vous pouvez utiliser map, il peut mapper des valeurs à partir d'un dictonairy ou même d'une fonction personnalisée.

Supposons que ceci soit votre df:

    ID First_Name Last_Name
0  103          a         b
1  104          c         d

Créez les dicts:

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

Et carte:

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

Le résultat sera:

    ID First_Name Last_Name
0  103       Matt     Jones
1  104         Mr         X

Ou utilisez une fonction personnalisée:

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

Cette question pourrait encore être visitée assez souvent pour que cela vaille la peine de proposer un addendum à la réponse de M. Kassies. La classe intégrée dict peut être sous-classée de sorte qu'une valeur par défaut soit renvoyée pour les clés "manquantes". Ce mécanisme fonctionne bien pour les pandas. mais voir ci-dessous.

De cette façon, il est possible d'éviter les erreurs clés.

>>> 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 même chose peut être faite plus simplement de la manière suivante. L'utilisation de l'argument 'default' pour la méthode get d'un objet dict évite d'avoir à sous-classer 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         
9
Bill Bell

La question initiale concerne un cas d'utilisation spécifique étroit. Pour ceux qui ont besoin de réponses plus génériques, voici quelques exemples:

Création d'une nouvelle colonne à l'aide des données d'autres colonnes

Étant donné le cadre de données ci-dessous:

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

Ci-dessous, nous ajoutons une nouvelle colonne description en tant que concaténation d'autres colonnes à l'aide de l'opération + Qui est remplacée pour la série. Le formatage de chaîne de fantaisie, les chaînes de caractères f, etc. ne fonctionneront pas ici car le + S'applique aux scalaires et non aux valeurs 'primitives':

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

Nous obtenons 1 years Pour le chat (au lieu de 1 year) Que nous allons corriger ci-dessous à l'aide de conditions.

Modification d'une colonne existante avec des conditions

Ici, nous remplaçons la colonne animal d'origine par des valeurs provenant d'autres colonnes et utilisons np.where pour définir une sous-chaîne conditionnelle basée sur la valeur de 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

Modification de plusieurs colonnes avec des conditions

Une approche plus souple consiste à appeler .apply() sur un cadre de données entier plutôt que sur une seule colonne:

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

Dans le code ci-dessus, la fonction transform_row(r) prend un objet Series représentant une ligne donnée (indiqué par axis=1, La valeur par défaut de axis=0 Fournira un objet Series pour chaque colonne). Cela simplifie le traitement car nous pouvons accéder aux valeurs "primitives" réelles de la ligne en utilisant les noms de colonne et avoir une visibilité sur les autres cellules de la ligne/colonne donnée.

8
ccpizza