在熊猫中根据另一个值更改一个值

我试图用 Python 复制我的 Stata 代码,我被指向熊猫的方向。然而,我很难理解如何处理这些数据。

假设我想迭代列标题‘ ID 中的所有值。如果这个 ID 匹配一个特定的数字,那么我想更改两个对应的值 FirstName 和 LastName。

在 Stata,情况是这样的:

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

因此,这将替换 FirstName 中与 ID = = 103到 Matt 的值对应的所有值。

在《熊猫》中,我正在尝试类似的东西

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

不知道接下来该怎么办,有什么想法吗?

391456 次浏览

一种选择是使用 Python 的切片和索引特性从逻辑上评估条件保存的位置,并覆盖那里的数据。

假设您可以使用 pandas.read_csv将数据直接加载到 pandas,那么下面的代码可能对您有帮助。

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

正如在评论中提到的,您还可以一次完成对两列的赋值:

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

请注意,需要使用 pandas版本0.11或更新的版本才能使用 loc来覆盖赋值操作。事实上,对于像0.8这样的老版本(尽管批评者可能会说) ,链式作业是 正确的一种方式,因此,即使在更现代的熊猫版本中应该避免,了解这一点也是有用的。


另一种方法是使用所谓的链式赋值。这种行为不太稳定,因此它不被认为是最佳解决方案(在文档中是 明确表示不鼓励) ,但是了解以下内容是有用的:

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

你可以使用 map,它可以从一个字典甚至一个自定义函数映射值。

假设这是你的 df:

    ID First_Name Last_Name
0  103          a         b
1  104          c         d

创建判决书:

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

还有地图:

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

结果将是:

    ID First_Name Last_Name
0  103       Matt     Jones
1  104         Mr         X

或者使用自定义函数:

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

这个问题可能仍然被频繁地提及,因此值得为卡西斯先生的回答提供一个附录。可以对 dict内置类进行子类化,以便返回“丢失”键的默认值。这种机制对熊猫很有效。但是看看下面。

这样就可以避免关键错误。

>>> 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

同样的事情可以用以下方法更简单地完成。使用“ default”参数作为 dict 对象的 get方法使得没有必要子类化 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

最初的问题解决了一个特定的狭义用例。对于那些需要更一般性答案的人,这里有一些例子:

使用来自其他列的数据创建新列

根据以下数据框架:

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

下面我们通过使用 +操作添加一个新的 description列作为其他列的串联,这个操作被系列重写。花哨的字符串格式、 f-string 等等在这里不起作用,因为 +适用于标量,而不是“原始”值:

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

我们得到 1 years的猫(而不是 1 year) ,我们将修复下面使用条件。

用条件语句修改现有列

在这里,我们用来自其他列的值替换原来的 animal列,并使用 np.where根据 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

用条件语句修改多列

一种更灵活的方法是对整个数据框架而不是单列调用 .apply():

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

在上面的代码中,transform_row(r)函数接受一个表示给定行的 Series对象(由 axis=1指示,axis=0的默认值将为每列提供一个 Series对象)。这简化了处理,因为可以使用列名访问行中的实际“原始”值,并且可以看到给定行/列中的其他单元格。

df['FirstName']=df['ID'].apply(lambda x: 'Matt' if x==103 else '')
df['LastName']=df['ID'].apply(lambda x: 'Jones' if x==103 else '')

我发现打印出每一行符合条件的地方更容易:

for n in df.columns:
if(np.where(df[n] == 103)):
print(n)
print(df[df[n] == 103].index)

如果有人正在寻找一种方法,根据每一行本身的某些逻辑条件来更改多行的值,那么将 .apply()与函数一起使用就是一种方法。

df = pd.DataFrame({'col_a':[0,0], 'col_b':[1,2]})


col_a  col_b
0      0      1
1      0      2


def func(row):
if row.col_a == 0 and row.col_b <= 1:
row.col_a = -1
row.col_b = -1
return row


df.apply(func, axis=1)


col_a  col_b
0     -1     -1 # Modified row
1      0      2

虽然 .apply()通常用于向数据框架添加新的行/列,但是它可以用于修改现有行/列的值。